diff --git a/trunk/Makefile b/trunk/Makefile new file mode 100644 index 0000000000..6ae37aca10 --- /dev/null +++ b/trunk/Makefile @@ -0,0 +1,145 @@ +VERSION=$(shell head -n 1 VERSION) + +all: tgz +sae: tgz build4sina build4sae +syun: tgz build4sina build4yunshangdian +edu: tgz build4edu +linux: tgz build4linux + +clean: + rm -fr zentaopms + rm -fr *.tar.gz + rm -fr *.zip + rm -fr api* + rm -fr build/linux/lampp + rm -fr sae + rm -fr syun +tgz: + # make the directories. + mkdir -p zentaopms/lib + mkdir -p zentaopms/db + mkdir -p zentaopms/bin + mkdir -p zentaopms/config + mkdir -p zentaopms/www/data/upload + # copy files. + cp -fr framework zentaopms/ + cp -fr lib/ zentaopms/ + cp -fr config/config.php zentaopms/config/ + cp -fr module zentaopms/ + cp -fr www/*.ico www/fusioncharts www/*.php www/js www/*.txt www/theme www/.htaccess www/.ztaccess zentaopms/www + cp bin/ztc* bin/computeburn.php bin/getbugs.php bin/initext.php bin/todo.php bin/backup.php bin/checkdb.php bin/minifyfront.php zentaopms/bin + cp -fr db zentaopms/ + cp -fr doc/* zentaopms/ + cp -fr tmp zentaopms/ + cp VERSION zentaopms/ + # combine js and css files. + cd zentaopms/bin/ && php ./minifyfront.php + # create the restart file for svn. + touch zentaopms/module/svn/restart + # touch the front.class.php to make it's mtime to new. + touch zentaopms/lib/front/front.class.php + # delee the unused files. + find zentaopms -name .svn |xargs rm -fr + find zentaopms -name tests |xargs rm -fr + # change mode. + chmod 777 -R zentaopms/tmp/ + chmod 777 -R zentaopms/www/data + chmod 777 -R zentaopms/config + chmod 777 zentaopms/module + chmod a+rx zentaopms/bin/* + find zentaopms/ -name ext |xargs chmod -R 777 + # zip it. + zip -r -9 ZenTaoPMS.$(VERSION).zip zentaopms + rm -fr zentaopms + +patchphpdoc: + sudo cp misc/doc/phpdoc/*.tpl /usr/share/php/data/PhpDocumentor/phpDocumentor/Converters/HTML/frames/templates/phphtmllib/templates/ +phpdoc: + phpdoc -d bin,framework,config,lib,module,www -t api -o HTML:frames:phphtmllib -ti ZenTaoPMSAPIοֲ -s on -pp on -i *test* + phpdoc -d bin,framework,config,lib,module,www -t api.chm -o chm:default:default -ti ZenTaoPMSAPIοֲ -s on -pp on -i *test* +doxygen: + doxygen misc/doc/doxygen/doxygen.conf +build4sina: + # unzip the zentaopms packae. + unzip ZenTaoPMS.$(VERSION).zip + rm -fr ZenTaoPMS.$(VERSION).zip + # move the files under www to zentaopms/ + mv zentaopms/www/* zentaopms + # replace the directory of index.php, install.php, upgrade.php. + sed -e 's/..\/framework/framework/g' zentaopms/index.php |sed -e "s/dirname(//" |sed -e 's/)))/))/' >zentaopms/index.php.new + sed -e 's/..\/framework/framework/g' zentaopms/install.php |sed -e "s/dirname(//" |sed -e 's/)))/))/' >zentaopms/install.php.new + grep -v myConfig zentaopms/upgrade.php | grep -v '{' | grep -v '}' | grep -v 'exit' | grep -v checkUpgradeStatus | grep -v debug> zentaopms/upgrade.php.new # remove the checking of myConfig. + sed -e 's/..\/framework/framework/g' zentaopms/upgrade.php.new | sed -e "s/dirname(//" |sed -e 's/)))/))/' > zentaopms/upgrade.php.new + mv zentaopms/index.php.new zentaopms/index.php + mv zentaopms/upgrade.php.new zentaopms/upgrade.php +build4sae: + # remove the data and tmp directory for sae. + rm -fr zentaopms/data zentaopms/www zentaopms/tmp + # process the install.php. + cat zentaopms/install.php.new |grep -v 'setDebug' > zentaopms/install.php + rm -fr zentaopms/install.php.new + # replace the error_log to sae_debug + sed -e 's/error_log/sae_debug/g' zentaopms/framework/router.class.php | sed -e "s/saveSQL/saveSQL4SAE/" >zentaopms/framework/router.class.php.new + mv zentaopms/framework/router.class.php.new zentaopms/framework/router.class.php + # append the savesql.php. + cat build/sae/savesql.php >> zentaopms/framework/helper.class.php + # change the logic of merge model file in helper.class.php. + sed -e 's/\$$app->getTmpRoot/"saemc:\/\/" . \$$app\-\>getTmpRoot/g' zentaopms/framework/helper.class.php >zentaopms/framework/helper.class.new + mv zentaopms/framework/helper.class.new zentaopms/framework/helper.class.php + cp build/sae/mysae.php zentaopms/config/my.php + cp build/sae/sae_app_wizard.xml zentaopms/ + # get the extension files. + svn export https://svn.cnezsoft.com/easysoft/trunk/zentaoext/sae + mv sae/lib/saestorage zentaopms/lib/ + cp -fr sae/* zentaopms/module/ + # create the package. + cp build/sae/config.yaml zentaopms/ + cd zentaopms && zip -r -9 ../ZenTaoPMS.$(VERSION).sae.zip * && cd - + rm -fr sae + rm -fr zentaopms +build4yunshangdian: + # rename the install.php. + mv zentaopms/install.php.new zentaopms/install.php + # move the .htaccess to zentaopms/ + mv zentaopms/www/.htaccess zentaopms/htaccess + # remove tmp, www, data, init them in my.php + rm -fr zentaopms/www + rm -fr zentaopms/tmp + rm -fr zentaopms/data + # copy the my.php + mkdir zentaopms/config/ext + cp build/sae/mysyun.php zentaopms/config/ext/syun.php + # copy the wizard.xml. + grep -v 'Storage' build/sae/sae_app_wizard.xml | grep -v 'Memcache' > zentaopms/sae_app_wizard.xml + # get the extension files. + svn export https://svn.cnezsoft.com/easysoft/trunk/zentaoext/syun + cp -fr syun/* zentaopms/module/ + # create the package. + cd zentaopms && zip -r -9 ../ZenTaoPMS.$(VERSION).syun.zip * && cd - + #rm -fr syun + #rm -fr zentaopms +build4linux: + unzip ZenTaoPMS.$(VERSION).zip + rm -fr ZenTaoPMS.$(VERSION).zip + sed -e 's/index.php/\/zentao\/index.php/g' zentaopms/www/.htaccess >zentaopms/www/.htaccess.new + mv zentaopms/www/.htaccess.new zentaopms/www/.htaccess + # build xmapp. + cd ./build/linux/ && ./buildxmapp.sh $(xampp) + mv ./build/linux/lampp ./ +saas: + mkdir backup + mkdir tmp/model + mkdir tmp/extension + mkdir www/data/upload -p + chmod 777 backup + chmod 777 -R tmp + chmod 777 -R www/data +build4edu: + unzip ZenTaoPMS.$(VERSION).zip + rm -fr ZenTaoPMS.$(VERSION).zip + # get the extension files. + svn export https://svn.cnezsoft.com/easysoft/trunk/zentaoext/edu + cp -fr edu/* zentaopms/ + # create the package. + zip -rm -9 ZenTaoPMS.$(VERSION).edu.zip zentaopms + rm -fr edu diff --git a/trunk/VERSION b/trunk/VERSION new file mode 100644 index 0000000000..3d5c46a93c --- /dev/null +++ b/trunk/VERSION @@ -0,0 +1 @@ +3.3.stable diff --git a/trunk/bin/backup.php b/trunk/bin/backup.php new file mode 100755 index 0000000000..6625263684 --- /dev/null +++ b/trunk/bin/backup.php @@ -0,0 +1,66 @@ +mysqldump)) +{ + echo "Please set the mysqldump in my.php:\n"; + echo "Just like: \n"; + echo '$config->mysqldump = \'/usr/bin/mysqldump\'; for linux' . "\n"; + echo '$config->mysqldump = \'D:\xampp\mysql\bin\mysqldump.exe\'; for windows' . "\n"; + exit; +} + +/* Init the backupRoot and dest directory. */ +$backupRoot = $pmsRoot . "/backup"; +$destDir = $backupRoot . "/" . date('Ym'); + +if(!file_exists($backupRoot)) mkdir($backupRoot, 0777); +if(!file_exists($destDir)) mkdir($destDir, 0777); + +/* Backup database. */ +$dbRawFile = "db." . date('Ymd') . ".sql"; +if($config->db->password) +{ + $command = "{$config->mysqldump} -u{$config->db->user} -p{$config->db->password} -P {$config->db->port} {$config->db->name} > {$dbRawFile}"; +} +else +{ + $command = "{$config->mysqldump} -u{$config->db->user} -P {$config->db->port} {$config->db->name} > {$dbRawFile}"; +} +echo "Backuping database,"; +system($command, $return); +if(!$return) +{ + $dbZipFile = $destDir . "/" . str_replace("sql", "zip", $dbRawFile); + $archive = new pclzip($dbZipFile); + if($archive->create($dbRawFile)) + { + unlink($dbRawFile); + echo " successfully saved to $dbZipFile\n"; + } + else + { + die("Error : " . $archive->errorInfo(true)); + } +} +else +{ + echo "Failed to backup database!\n"; +} + +/* Backup the data. */ +chdir(dirname(dirname(__FILE__)) . "/www"); +if(!is_dir('data/upload')) die(" No files needed backup.\n"); + +$dataFile = $destDir . "/" . "file." . date('Ymd', time()) . ".zip"; +$archive = new pclzip($dataFile); +echo "Backuping files,"; +if($archive->create("data/upload", PCLZIP_OPT_REMOVE_PATH, "data")) die(" successfully saved to $dataFile\n"); +die("Error : ".$archive->errorInfo(true)); diff --git a/trunk/bin/build.php b/trunk/bin/build.php new file mode 100644 index 0000000000..778159ab19 --- /dev/null +++ b/trunk/bin/build.php @@ -0,0 +1,45 @@ +zentao.sql +8. zip包。 +9. windows包。 +10. 上传文件。 +11. 撰写升级声明。 + */ + +$phpURL = 'http://zentaophp.googlecode.com/svn/tags/'; +$pmsURL = 'http://ZenTaoPMS.googlecode.com/svn/tags/'; + +$phpTag = getLatestTag($phpURL); +$pmsTag = getLatestTag($pmsURL); + +$phpTagURL = $phpURL . $phpTag; +$pmsTagURL = $pmsURL . $pmsTag; + +echo $phpTag . "'\t" . $pmsTag . "\n"; + +chdir('../release/'); +echo `svn export $phpTagURL`; +echo `svn export $pmsTagURL`; + +/* Get the latest tag under a url. */ +function getLatestTag($url) +{ + $lines = file($url); + $latestTag = ''; + foreach($lines as $line) + { + if(strpos($line, '
  • ') !== false) $latestTag = $line; + } + $latestTag = explode('"', $latestTag); + $latestTag = $latestTag[1]; + return $latestTag; +} diff --git a/trunk/bin/check.php b/trunk/bin/check.php new file mode 100755 index 0000000000..034638edac --- /dev/null +++ b/trunk/bin/check.php @@ -0,0 +1,126 @@ +getMethods(); + foreach($methods as $method) + { + $methodRef = new ReflectionMethod($method->class, $method->name); + if($methodRef->isPublic() and strpos($method->name, '__') === false) + { + $methodName = $method->name; + if(in_array($moduleName . '-' . strtolower($method->name), $whiteList)) continue; + + $exits = false; + foreach($lang->resource->$moduleName as $key => $label) + { + if(strtolower($methodName) == strtolower($key)) $exits = true; + } + if(!$exits) echo $moduleName . "\t" . $methodName . " not in the list. \n"; + } + } + } + } +} + +/* checking actions of every module. */ +echo '-------------lang checking-----------------' . "\n"; +include '../config/config.php'; +foreach(glob($moduleRoot . '*') as $modulePath) +{ + unset($lang); + $moduleName = basename($modulePath); + $mainLangFile = $modulePath . '/lang/zh-cn.php'; + if(!file_exists($mainLangFile)) continue; + $mainLines = file($mainLangFile); + + foreach($config->langs as $langKey => $langName) + { + if($langKey == 'zh-cn' or $langKey == 'zh-tw') continue; + $langFile = $modulePath . '/lang/' . $langKey . '.php'; + if(!file_exists($langFile)) continue; + $lines = file($langFile); + foreach($mainLines as $lineNO => $line) + { + if(strpos($line, '$lang') === false) + { + //if($line != $lines[$lineNO]) echo $moduleName . ' ' . $langKey . ' ' . $lineNO . "\n"; + } + else + { + list($mainKey, $mainValue) = explode('=', $line); + list($key, $value) = explode('=', $lines[$lineNO]); + if(trim($mainKey) != trim($key)) + { + $key = trim($key); + $lineNO = $lineNO + 1; + echo "module $moduleName need checking, command is:"; + echo " vim -O +$lineNO ../module/$moduleName/lang/zh-cn.php +$lineNO ../module/$moduleName/lang/en.php \n"; + break; + } + } + } + } +} +?> diff --git a/trunk/bin/checkDemoData.php b/trunk/bin/checkDemoData.php new file mode 100755 index 0000000000..31e8c3bf6d --- /dev/null +++ b/trunk/bin/checkDemoData.php @@ -0,0 +1,14 @@ +db->prefix . 'config') === false and + strpos($sql, $config->db->prefix . 'company') === false and + strpos($sql, $config->db->prefix . 'group') === false + ) $output .= $sql; +} +file_put_contents("../db/demo.sql", $output); + diff --git a/trunk/bin/checkdb.php b/trunk/bin/checkdb.php new file mode 100755 index 0000000000..5064600779 --- /dev/null +++ b/trunk/bin/checkdb.php @@ -0,0 +1,16 @@ +requestType == 'PATH_INFO') +{ + system('php ztcli "http://localhost/admin-checkdb"', $requestVar); +} +elseif($config->requestType == 'GET') +{ + system('php ztcli "http://localhost/?m=admin&f=checkdb"', $requestVar); +} + +if(!$requestVar) +{ + echo "Check DataBase successfully!\n"; +} diff --git a/trunk/bin/checkyaml.php b/trunk/bin/checkyaml.php new file mode 100644 index 0000000000..87a163be5d --- /dev/null +++ b/trunk/bin/checkyaml.php @@ -0,0 +1,28 @@ + $release) +{ + if(empty($release['zentaoversion'])) die("version $version should set the compatible zentao versions\n"); + if(!preg_match('/^(free|share|opensource|business)$/', $release['charge'])) die("version $version's charge field shoulde be free, share, opensource or business\n"); + if(empty($release['license'])) die("version $version 's license should be set\n"); + if(empty($release['date']) ) die("version $version 's date field should be set\n"); +} diff --git a/trunk/bin/cn2tw.php b/trunk/bin/cn2tw.php new file mode 100644 index 0000000000..8af43a45b4 --- /dev/null +++ b/trunk/bin/cn2tw.php @@ -0,0 +1,18 @@ +#!/usr/bin/env php + $targetLangFile"); + $defaultLang = file_get_contents($targetLangFile); + $targetLang = str_replace('zh-cn', $langDesc, $defaultLang); + file_put_contents($targetLangFile, $targetLang); +} +?> diff --git a/trunk/bin/computeburn.php b/trunk/bin/computeburn.php new file mode 100755 index 0000000000..6dc06089d9 --- /dev/null +++ b/trunk/bin/computeburn.php @@ -0,0 +1,49 @@ +#!/usr/bin/env php +zentao->root = ""; // 禅道访问的完整路径,包括后面的斜线。比如http://pms.zentao.net/ +$config->zentao->account = ""; // 可以访问禅道的帐号,需要有超级model调用接口的访问权限。 +$config->zentao->password = ""; // 密码。 + +class computeburn +{ + public $config; // the config var. + public $zentao; // the zentao client. + + public function __construct($config) + { + $this->initConfig($config); + $this->initZenTao(); + } + + /* run. */ + public function run() + { + $result = $this->zentao->fetchModel('project', 'computeburn'); + if(empty($result)) return; + foreach($result as $burns) + { + echo $burns->project . "\t"; + echo $burns->projectName . "\t"; + echo $burns->date . "\t"; + echo $burns->left . "\n"; + } + } + + /* Init the config. */ + private function initConfig($config) + { + $this->config = $config; + } + + /* Init the client of zentao api. */ + private function initZenTao() + { + $this->zentao = new ztclient($this->config->zentao->root, $this->config->zentao->account, $this->config->zentao->password); + } +} + +$computeburn = new computeburn($config); +$computeburn->run(); +?> diff --git a/trunk/bin/copylang.php b/trunk/bin/copylang.php new file mode 100755 index 0000000000..8f7d1a34ce --- /dev/null +++ b/trunk/bin/copylang.php @@ -0,0 +1,17 @@ +#!/usr/bin/env php + diff --git a/trunk/bin/exportactions.php b/trunk/bin/exportactions.php new file mode 100755 index 0000000000..3bf58bdd0b --- /dev/null +++ b/trunk/bin/exportactions.php @@ -0,0 +1,29 @@ +getMethods(); + foreach($methods as $method) + { + $methodRef = new ReflectionMethod($method->class, $method->name); + if($methodRef->isPublic() and strpos($method->name, '__') === false) + { + echo "\$lang['action']['$moduleName']['$method->name'] = '$method->name';\n"; + } + } + echo "\n"; + } + } +} +?> diff --git a/trunk/bin/getallcommon.php b/trunk/bin/getallcommon.php new file mode 100644 index 0000000000..6bea6b1765 --- /dev/null +++ b/trunk/bin/getallcommon.php @@ -0,0 +1,28 @@ + $maxLength) $maxLength = strlen($moduleName); +} + +foreach($modules as $modulePath) +{ + $moduleName = basename($modulePath); + if($moduleName == 'help' or $moduleName == 'editor') continue; + $langFile = $modulePath . "/lang/$langType.php"; + if(!file_exists($langFile)) continue; + include $langFile; + + $moduleTitle = ''; + if(isset($lang->$moduleName->common)) + { + $moduleTitle = $lang->$moduleName->common; + } + + echo "\$lang->editor->modules['$moduleName'] " . str_pad('', $maxLength - strlen($moduleName)) . "= '$moduleTitle';\n"; + +} diff --git a/trunk/bin/getbugs.php b/trunk/bin/getbugs.php new file mode 100755 index 0000000000..bd368514af --- /dev/null +++ b/trunk/bin/getbugs.php @@ -0,0 +1,77 @@ +#!/usr/bin/env php +fetch($sessionAPI); +$session = json_decode($snoopy->results); +$session = json_decode($session->data); + +/*用户登录*/ +$authHash = md5(md5($password) . $session->rand); +$submitVars["account"] = $account; +$submitVars["password"] = $authHash; +$snoopy->cookies[$session->sessionName] = $session->sessionID; +$snoopy->submit($loginAPI, $submitVars); + +/* 直接调用my模块的bugs页面。*/ +$snoopy->fetch($myBugAPI . "&$session->sessionName=$session->sessionID"); +$result = json_decode($snoopy->results); + +if($result->status == 'success' && md5($result->data) == $result->md5) +{ + $bugs = json_decode($result->data)->bugs; +} +else +{ + echo "called failed or transfered not complete."; + exit; +} + +if($bugs) +{ + foreach($bugs as $bug) echo $bug->id . "\t" . $bug->title . "\n"; +} +else +{ + echo 'no bugs' . "\n"; +} + +/* 通过超级model调用。*/ +$snoopy->fetch($superMyBugAPI . "&$session->sessionName=$session->sessionID"); +$result = json_decode($snoopy->results); +if(is_object($result)) +{ + foreach($result as $id=>$bug) echo $id . "\t" . $bug . "\n"; +} +else +{ + echo 'no bugs' . "\n"; +} +?> diff --git a/trunk/bin/initext.php b/trunk/bin/initext.php new file mode 100644 index 0000000000..22dc544af0 --- /dev/null +++ b/trunk/bin/initext.php @@ -0,0 +1,60 @@ + + * @package common + * @version $Id$ + * @link http://www.zentao.net + */ +include '../config/config.php'; + +$modules = array(); +$moduleRoot = realpath('../module/') . '/'; + +if(is_dir($moduleRoot)) +{ + if($dh = opendir($moduleRoot)) + { + while($module = readdir($dh)) + { + if(strpos(basename($module), '.') === false) $modules[] = $module; + } + closedir($dh); + } +} +else +{ + die("The module you input does not exist. \n"); +} + +foreach($modules as $module) +{ + /* 设定各个目录。*/ + $extRoot = $moduleRoot . DIRECTORY_SEPARATOR. $module . DIRECTORY_SEPARATOR . 'ext'; + $extControl = $extRoot . DIRECTORY_SEPARATOR . 'control'; + $extModel = $extRoot . DIRECTORY_SEPARATOR . 'model'; + $extView = $extRoot . DIRECTORY_SEPARATOR . 'view'; + $extConfig = $extRoot . DIRECTORY_SEPARATOR . 'config'; + $extLang = $extRoot . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR; + + /* 建立各个扩展目录 */ + if(!file_exists($extRoot)) mkdir($extRoot, 0777); + if(!file_exists($extControl)) mkdir($extControl, 0777); + if(!file_exists($extModel)) mkdir($extModel, 0777); + if(!file_exists($extView)) mkdir($extView, 0777); + if(!file_exists($extConfig)) mkdir($extConfig, 0777); + if(!file_exists($extLang)) mkdir($extLang, 0777); + + /* 创建语言目录。*/ + $langs = array_keys($config->langs); + foreach($langs as $lang) + { + $langPath = $extLang . $lang; + if(!file_exists($langPath)) mkdir($langPath, 0777); + } + + echo "init $module ... \n"; +} diff --git a/trunk/bin/minifyfront.php b/trunk/bin/minifyfront.php new file mode 100644 index 0000000000..13dadb688e --- /dev/null +++ b/trunk/bin/minifyfront.php @@ -0,0 +1,70 @@ +langs); +$themes = array_keys($lang->themes); + +/* Create css files for every them and every lang. */ +foreach($langs as $lang) +{ + foreach($themes as $theme) + { + /* Common css files. */ + $cssCode = file_get_contents($themeRoot . 'default/yui.css'); + $cssCode .= file_get_contents($themeRoot . 'default/style.css'); + $cssCode .= file_get_contents($themeRoot . 'default/colorbox.css'); + $cssCode .= file_get_contents($themeRoot . 'default/chosen.css'); + $cssCode .= file_get_contents($themeRoot . 'default/treeview.css'); + $cssCode .= file_get_contents($themeRoot . 'default/datepicker.css'); + $cssCode .= file_get_contents($themeRoot . 'default/alert.css'); + + /* Css file for current lang and current them. */ + $cssCode .= file_get_contents($themeRoot . "lang/$lang.css"); + if($theme != 'default') $cssCode .= file_get_contents($themeRoot . $theme . '/style.css'); + + /* Combine them. */ + $cssFile = $themeRoot . "default/$lang.$theme.css"; + file_put_contents($cssFile, $cssCode); + + /* Compress it. */ + `java -jar ~/bin/yuicompressor/build/yuicompressor.jar --type css $cssFile -o $cssFile`; + } +} diff --git a/trunk/bin/preparetest.php b/trunk/bin/preparetest.php new file mode 100644 index 0000000000..bce62e92d7 --- /dev/null +++ b/trunk/bin/preparetest.php @@ -0,0 +1,60 @@ + $database.sql;\n"; + $cmd .= "mysql -uroot $backupDatabase < $database.sql;\n"; + $cmd .= "rm -fr $database.sql"; + system($cmd); + } +} + +/* Restore stored backup databases. */ +function restore() +{ + global $databases; + foreach($databases as $database) + { + $backupDatabase = "back_$database"; + mysql_query("DROP DATABASE $database"); + mysql_query("CREATE DATABASE $database"); + $cmd = "mysqldump -u root $backupDatabase > $database.sql;\n"; + $cmd .= "mysql -uroot $database < $database.sql;\n"; + $cmd .= "rm -fr $database.sql"; + system($cmd); + } +} + +/* Connect to database. */ +function connectDB() +{ + global $config; + mysql_connect($config->db->host, $config->db->user, $config->db->password); +} diff --git a/trunk/bin/syncext.php b/trunk/bin/syncext.php new file mode 100644 index 0000000000..8f41494aff --- /dev/null +++ b/trunk/bin/syncext.php @@ -0,0 +1,100 @@ +#!/usr/bin/env php +zentaophp->svnRoot = 'https://zentaophp.googlecode.com/svn/'; +$config->zentaophp->svnTrunk = 'trunk/'; +$config->zentaophp->svnTags = 'tags/'; + +$config->zentaopms->svnRoot = 'https://ZenTaoPMS.googlecode.com/svn/'; +$config->zentaopms->svnTrunk = 'trunk/'; +$config->zentaopms->svnTags = 'tags/'; + +if(count($argv) != 4) die(__FILE__ . " repo version releasetype:beta|alpa|stable\n"); + +$repo = $argv[1]; +$version = $argv[2]; +$release = $argv[3]; + +$sourceURL = $config->$repo->svnRoot . $config->$repo->svnTrunk; +$targetURL = $config->$repo->svnRoot . $config->$repo->svnTags . $repo . '_' . $version . '_' . $release . '_' . date('Ymd'); + +$svnCMD = "svn rm $targetURL -m 'remove it'; svn cp $sourceURL $targetURL -m 'tag $version of $repo'"; +system($svnCMD); diff --git a/trunk/bin/todo.php b/trunk/bin/todo.php new file mode 100755 index 0000000000..b4f22e4bff --- /dev/null +++ b/trunk/bin/todo.php @@ -0,0 +1,72 @@ +#!/usr/bin/env php +fetch($sessionAPI); +$session = json_decode($snoopy->results); +$session = json_decode($session->data); + +/*用户登录,加密验证。*/ +$authHash = md5(md5($password) . $session->rand); +$submitVars["account"] = $account; +$submitVars["password"] = $authHash; +$snoopy->cookies[$session->sessionName] = $session->sessionID; +$snoopy->submit($loginAPI, $submitVars); + +/* 直接调用my模块的todo页面。*/ +$snoopy->fetch($myTodoAPI . "&$session->sessionName=$session->sessionID"); +$result = json_decode($snoopy->results); + +if($result->status == 'success' && md5($result->data) == $result->md5) +{ + $todos = json_decode($result->data)->todos; +} +else +{ + echo "called failed or transfered not complete."; + exit; +} + +if($todos) +{ + foreach($todos as $todo) + { + echo $todo->id . "\t" . + $todo->type . "\t" . + $todo->pri . "\t" . + $todo->name . "\t" . + $todo->status . "\n"; + } +} +else +{ + echo "no todos.\n"; +} +?> diff --git a/trunk/bin/ztcli b/trunk/bin/ztcli new file mode 100755 index 0000000000..b139bab174 --- /dev/null +++ b/trunk/bin/ztcli @@ -0,0 +1,46 @@ +#!/usr/bin/env php + + * @package bin + * @version $Id$ + * @link http://www.ZenTaoPMS.com + */ +//error_reporting(0); +define('IN_SHELL', true); + +/* Judge the args. */ +if($argc != 2) die('Usage: ' . basename(__FILE__) . " \n"); + +/* Parse the request into params. */ +$request = parse_url(trim($argv[1])); +$_SERVER['HTTP_HOST'] = $request['host']; + +/* Load the framework. */ +chdir(dirname(__FILE__)); +include '../framework/router.class.php'; +include '../framework/control.class.php'; +include '../framework/model.class.php'; +include '../framework/helper.class.php'; +include '../config/config.php'; + +if($config->requestType == 'PATH_INFO') +{ + $_SERVER['PATH_INFO'] = str_replace($config->webRoot, '', $request['path']); +} +else +{ + parse_str($request['query'], $_GET); + $_SERVER['SCRIPT_NAME'] = $_SERVER['HTTP_HOST'] . 'index.php'; + $_SERVER['REQUEST_URI'] = isset($request['query']) ? $request['query'] : ''; +} + +/* Instance the app and run it. */ +$app = router::createApp('pms', dirname(dirname(__FILE__))); +$common = $app->loadCommon(); +$app->parseRequest(); +$app->loadModule(); diff --git a/trunk/bin/ztcli.bat b/trunk/bin/ztcli.bat new file mode 100644 index 0000000000..68635d88b4 --- /dev/null +++ b/trunk/bin/ztcli.bat @@ -0,0 +1 @@ +php ztcli %* diff --git a/trunk/bin/ztcli.sh b/trunk/bin/ztcli.sh new file mode 100755 index 0000000000..cdeb151459 --- /dev/null +++ b/trunk/bin/ztcli.sh @@ -0,0 +1 @@ +php ztcli $* diff --git a/trunk/build/linux/Makefile b/trunk/build/linux/Makefile new file mode 100644 index 0000000000..f26aafc820 --- /dev/null +++ b/trunk/build/linux/Makefile @@ -0,0 +1,20 @@ +VERSION=$(shell head -n 1 zentao/VERSION) + +all: 7z +7z: + sudo ./lampp stop + sudo rm -fr logs/* + sudo rm -fr var/mysql/*.err + sudo rm -fr var/mysql/ib* + sudo mkdir .package + sudo mv * .package + sudo mv .package lampp + sudo mv lampp/Makefile . + sudo chmod a+rx lampp/start* + sudo chmod a+rx lampp/start88 + sudo 7z a -sfx ZenTaoPMS.${VERSION}.linux.7z lampp +clean: + sudo mv lampp lampp.bak + sudo mv lampp.bak/* . + sudo rm -fr *.7z + sudo rm -fr lampp.bak diff --git a/trunk/build/linux/buildxmapp.sh b/trunk/build/linux/buildxmapp.sh new file mode 100755 index 0000000000..68bee2b85f --- /dev/null +++ b/trunk/build/linux/buildxmapp.sh @@ -0,0 +1,268 @@ +tar zxvf $1/xampp.tar.gz +cd lampp + +# rm useless files. +rm -fr RELEASENOTES +rm -fr error +rm -fr icons +rm -fr logs/* +rm -fr htdocs/* +rm -fr phpmyadmin +rm -fr cgi-bin +rm -fr libexec +rm -fr tmp/* +chmod -R 777 tmp + +# rm useless settings. +rm -fr etc/proftpd.conf +rm -fr etc/webalizer.conf* +rm -fr etc/freetds.conf +rm -fr etc/openssl.cnf +rm -fr etc/php.ini-pre1.7.2 +rm -fr etc/pear.conf +rm -fr etc/pool.conf +rm -fr etc/openldap +rm -fr etc/original +rm -fr etc/httpd.conf.bak +rm -fr etc/lampp/startftp +rm -fr etc/ssl* +rm -fr etc/extra/httpd-dav.conf +rm -fr etc/extra/httpd-info.conf +rm -fr etc/extra/httpd-manual.conf +rm -fr etc/extra/httpd-ssl.conf +rm -fr etc/extra/httpd-manual.conf +rm -fr etc/extra/httpd-userdir.conf +rm -fr etc/extra/httpd-multilang-errordoc.conf +rm -fr etc/lampp/startssl + +# process httpd conf +grep -v '#' etc/httpd.conf | \ +grep -v '^$' | \ +grep -v 'mod_actions.so' | \ +grep -v 'authn_dbm' | \ +grep -v 'authn_anon' | \ +grep -v 'authn_dbd' | \ +grep -v 'authn_default' | \ +grep -v 'authz_groupfile' | \ +grep -v 'authz_dbm' | \ +grep -v 'authz_owner' | \ +grep -v 'authnz_ldap' | \ +grep -v 'authz_default' | \ +grep -v 'auth_digest_module' | \ +grep -v 'cache.so' | \ +grep -v 'mod_bucketeer' | \ +grep -v 'mod_dumpio' | \ +grep -v 'mod_echo' | \ +grep -v 'mod_case*' | \ +grep -v 'mod_ext_filter*' | \ +grep -v 'mod_include' | \ +grep -v 'mod_filter' | \ +grep -v 'mod_charset_lite' | \ +grep -v 'mod_ldap' | \ +grep -v 'mod_log_config' | \ +grep -v 'mod_logio' | \ +grep -v 'mod_cern_meta' | \ +grep -v 'mod_headers' | \ +grep -v 'mod_ident' | \ +grep -v 'mod_usertrack' | \ +grep -v 'mod_unique_id' | \ +grep -v 'mod_proxy*' | \ +grep -v 'mod_dav*' | \ +grep -v 'mod_status' | \ +grep -v 'mod_asis' | \ +grep -v 'mod_info' | \ +grep -v 'mod_suexec' | \ +grep -v 'mod_cgi*' | \ +grep -v 'mod_negotiation' | \ +grep -v 'mod_imagemap' | \ +grep -v 'mod_speling' | \ +grep -v 'mod_userdir' | \ +grep -v 'mod_apreq2' | \ +grep -v 'mod_ssl' | \ +grep -v 'multilang-errordoc' > etc/httpd.conf.lite +mv etc/httpd.conf.lite etc/httpd.conf + +# process my.cnf +grep -v '^innodb' etc/my.cnf| \ +sed -e 's/#skip-innodb/default-storage-engine=MyISAM\nskip-innodb/' | \ +grep -v '^#' | \ +grep -v '^$' > etc/my.cnf.new +mv etc/my.cnf.new etc/my.cnf + +# process php.ini +echo 'zend_extension = /opt/lampp/lib/php/extensions/no-debug-non-zts-20090626/ioncube_loader_lin_5.3.so' > etc/php.ini.new +grep -v '^;' etc/php.ini |\ +grep -v '^$' |\ +grep -v 'sqlite.so' |\ +grep -v 'radius.so' |\ +grep -v 'pgsql.so' |\ +grep -v 'ming.so' |\ +grep -v 'ncurses.so' >> etc/php.ini.new +mv etc/php.ini.new etc/php.ini + +# rm useless binaries. +mv bin bin.bak +mkdir bin +cd bin.bak +cp htpasswd apachectl apxs my_print_defaults mysql mysql.server mysqld_safe mysqldump php php-config phpize httpd ../bin/ +cd ../ +rm -fr bin.bak + +# fix bug of the mysqld_safe +sed -e 's/\/opt\/lampp\/\/opt\/lampp\/sbin/\/opt\/lampp\/sbin/g' bin/mysqld_safe > bin/mysqld_safe.new +mv bin/mysqld_safe.new bin/mysqld_safe +chmod a+rx bin/mysqld_safe + +# rm useless binaries at sbin directory, keep mysqld. +mv sbin sbin.bak +mkdir sbin +mv sbin.bak/mysqld sbin/ +rm -fr sbin.bak + +# process share directory. keep english and lampp directory. +mv share share.bak +mkdir share +mv share.bak/english share/ +mv share.bak/lampp share +echo >share/lampp/allons +rm -fr share.bak + +rm -fr share/lampp/alladdons +touch share/lampp/alladdons +chmod a+rx share/lampp/alladdons + +# process lib directory. +mv lib/php/extensions lib/phpextensions +rm -fr lib/php +mkdir lib/php +mv lib/phpextensions lib/php/extensions +rm -fr lib/php/extensions/no-debug-non-zts-20090626/interbase.so +rm -fr lib/php/extensions/no-debug-non-zts-20090626/ming.so +rm -fr lib/php/extensions/no-debug-non-zts-20090626/ncurses.so +rm -fr lib/php/extensions/no-debug-non-zts-20090626/oci8.so +rm -fr lib/php/extensions/no-debug-non-zts-20090626/pgsql.so +rm -fr lib/php/extensions/no-debug-non-zts-20090626/radius.so +rm -fr lib/php/extensions/no-debug-non-zts-20090626/sqlite.so +rm -fr lib/perl5 +rm -fr lib/proftpd +rm -fr lib/fonts +rm -fr apr-util-1 +rm -fr apr.exp +rm -fr aprutil.exp +rm -fr engines +rm -fr fips_premain.c +rm -fr fips_premain.c.sha1 +rm -fr gettext +rm -fr icu +rm -fr libapr-1* +rm -fr libapreq2* +rm -fr libdb.so.3 +rm -fr libform.so* +rm -fr libicule.so* +rm -fr libiculx.* +rm -fr libicutu.* +rm -fr liblber* +rm -fr libldap_r* +rm -fr libmcrypt +rm -fr libmenu.so* +rm -fr libming.so* +rm -fr libmysqlclient.* +rm -fr libncurses.so* +rm -fr libpanel.so* +rm -fr libpbmscl.so* +rm -fr libpng.so* +rm -fr libsablot.so* +rm -fr libsqlite* +rm -fr libtds.so* +rm -fr libtdssrv.so* +rm -fr libxslt-plugins +rm -fr libzzip-0* +rm -fr libzzipwrap* +rm -fr pkgconfig +rm -fr plugin +rm -fr stjR6sfX +rm -fr terminfo +rm -fr xml2Conf.sh +rm -fr xsltConf.sh + +# process var directory. +rm -fr var/perl +rm -fr var/proftpd* +rm -fr var/mysql/cdcol +rm -fr var/mysql/*.err +rm -fr var/mysql/ib* +rm -fr var/mysql/phpmyadmin +rm -fr var/mysql/test +chmod -R 777 var/mysql + +# process modules directory. +rm -fr modules/mod_perl.so +rm -fr modules/mod_actions.so +rm -fr modules/mod_asis.so +rm -fr modules/mod_authn_dbm +rm -fr modules/mod_authn_anon +rm -fr modules/mod_authn_dbd +rm -fr modules/mod_authn_default +rm -fr modules/mod_authz_dbm.so +rm -fr modules/mod_authz_default.so +rm -fr modules/mod_authz_groupfile.so +rm -fr modules/mod_authz_owner.so +rm -fr modules/mod_bucketeer.so +rm -fr modules/mod_cache* +rm -fr modules/mod_case** +rm -fr modules/mod_cern_meta.so +rm -fr modules/mod_cgi* +rm -fr modules/mod_charset_lite.so +rm -fr modules/mod_dav*.so +rm -fr modules/mod_dbd.so +rm -fr modules/mod_disk_cache.so +rm -fr modules/mod_dumpio.so +rm -fr modules/mod_ext_filter.so +rm -fr modules/mod_file_cache.so +rm -fr modules/mod_filter.so +rm -fr modules/mod_headers.so +rm -fr modules/mod_ident.so +rm -fr modules/mod_imagemap.so +rm -fr modules/mod_include.so +rm -fr modules/mod_info.so +rm -fr modules/mod_ldap.so +rm -fr modules/mod_log*.so +rm -fr modules/mod_mem_cache.so +rm -fr modules/mod_negotiation.so +rm -fr modules/mod_proxy* +rm -fr modules/mod_reqtimeout.so +rm -fr modules/mod_speling.so +rm -fr modules/mod_ssl.so +rm -fr modules/mod_status.so +rm -fr modules/mod_substitute.so +rm -fr modules/mod_suexec.so +rm -fr modules/mod_unique_id.so +rm -fr modules/mod_userdir.so +rm -fr modules/mod_usertrack.so +rm -fr modules/mod_version.so + +# copy customized xmapp config to etc/extra +cp ../zentao.conf etc/extra/httpd-xampp.conf + +# copy the zentao code. +mv ../../../zentaopms ./zentao + +# copy needed files. +cp ../Makefile . +cp ../start . +cp ../start88 . +cp ../stop . +cp ../../windows/xampp/index.php htdocs/ + +# copy sqlbuddy. +mkdir admin +unzip $1/sqlbuddy.zip +mv sqlbuddy admin/ + +# make the auth file +mkdir auth +touch auth/users +echo 'use htpasswd users username password to add a new user.' > auth/readme + +# copy the ioncube loader. +cp ../ioncube_loader_lin_5.3.so lib/php/extensions/no-debug-non-zts-20090626/ diff --git a/trunk/build/linux/ioncube_loader_lin_5.3.so b/trunk/build/linux/ioncube_loader_lin_5.3.so new file mode 100755 index 0000000000..5f4811b4e2 Binary files /dev/null and b/trunk/build/linux/ioncube_loader_lin_5.3.so differ diff --git a/trunk/build/linux/start b/trunk/build/linux/start new file mode 100644 index 0000000000..97090f9176 --- /dev/null +++ b/trunk/build/linux/start @@ -0,0 +1,24 @@ +#!/bin/bash +if test "`id -u`" -ne 0 +then + echo "You need to start XAMPP as root!" + exit +fi + +sed -e 's/88/80/g' etc/httpd.conf > etc/httpd.conf.80 +mv etc/httpd.conf.80 etc/httpd.conf + +sed -e 's/88/80/g' etc/extra/httpd-vhosts.conf > etc/extra/httpd-vhosts.conf.80 +mv etc/extra/httpd-vhosts.conf.80 etc/extra/httpd-vhosts.conf + +sed -e 's/3308/3306/g' etc/my.cnf > etc/my.cnf.3306 +mv etc/my.cnf.3306 etc/my.cnf + +sed -e 's/3308/3306/g' zentao/config/my.php > zentao/config/my.php.3306 +mv zentao/config/my.php.3306 zentao/config/my.php + +chown nobody -R var/mysql +chmod 777 tmp +chmod 777 -R zentao/tmp +chmod 777 -R zentao/www/data/upload +./lampp start diff --git a/trunk/build/linux/start88 b/trunk/build/linux/start88 new file mode 100755 index 0000000000..46163da4ce --- /dev/null +++ b/trunk/build/linux/start88 @@ -0,0 +1,24 @@ +#!/bin/bash +if test "`id -u`" -ne 0 +then + echo "You need to start XAMPP as root!" + exit +fi + +sed -e 's/80/88/g' etc/httpd.conf > etc/httpd.conf.88 +mv etc/httpd.conf.88 etc/httpd.conf + +sed -e 's/80/88/g' etc/extra/httpd-vhosts.conf > etc/extra/httpd-vhosts.conf.88 +mv etc/extra/httpd-vhosts.conf.88 etc/extra/httpd-vhosts.conf + +sed -e 's/3306/3308/g' etc/my.cnf > etc/my.cnf.3308 +mv etc/my.cnf.3308 etc/my.cnf + +sed -e 's/3306/3308/g' zentao/config/my.php > zentao/config/my.php.3308 +mv zentao/config/my.php.3308 zentao/config/my.php + +chown nobody -R var/mysql +chmod 777 tmp +chmod 777 -R zentao/tmp +chmod 777 -R zentao/www/data/upload +./lampp start diff --git a/trunk/build/linux/stop b/trunk/build/linux/stop new file mode 100644 index 0000000000..6152930b7c --- /dev/null +++ b/trunk/build/linux/stop @@ -0,0 +1,8 @@ +#!/bin/bash +if test "`id -u`" -ne 0 +then + echo "You need to start XAMPP as root!" + exit +fi + +./lampp stop diff --git a/trunk/build/linux/zentao.conf b/trunk/build/linux/zentao.conf new file mode 100644 index 0000000000..ad27672db8 --- /dev/null +++ b/trunk/build/linux/zentao.conf @@ -0,0 +1,37 @@ +# add support for php. +LoadModule php5_module modules/libphp5.so +AddType application/x-httpd-php .php .php3 .php4 + +# adjust the mime settings. +AddType image/x-icon .ico +AddType image/gif .ico +AddType image/jpeg .jpg .jpeg +AddType image/png .png +AddType application/javascript .js + +# setting for zentao. +Alias /zentao "/opt/lampp/zentao/www/" + + Options Indexes FollowSymLinks ExecCGI Includes + AllowOverride All + + +# setting for admin +Alias /sqlbuddy "/opt/lampp/admin/sqlbuddy" + + #Order allow,deny + #Allow from 127.0.0.1 + AuthName 'zentao admin' + AuthType Basic + AuthUserFile /opt/lampp/auth/users + require valid-user + + +# setting of gzip. + + DeflateCompressionLevel 9 + AddOutputFilterByType DEFLATE text/html text/css application/javascript + + +# turn off etag. +FileEtag none diff --git a/trunk/build/sae/config.yaml b/trunk/build/sae/config.yaml new file mode 100644 index 0000000000..0bb3f31de6 --- /dev/null +++ b/trunk/build/sae/config.yaml @@ -0,0 +1,17 @@ +name: zentao +version: 1 +cron: + - description: cron + url: project-computeburn-admin.html + schedule: every day of month 00:00 + timezone: Beijing +handle: + - rewrite: if(!is_dir() && !is_file()) goto "index.php/%{REQUEST_URI}" + - compress: if(out_header["Content-Type"] ~ "text") compress + - compress: if(out_header["Content-Type"] ~ "javascript") compress + - expire: if(path ~ "\.js$") time 360000 + - expire: if(path ~ "\.css$") time 360000 + - expire: if(path ~ "\.gif$") time 360000 + - expire: if(path ~ "\.png$") time 360000 + - expire: if(path ~ "\.ico$") time 360000 + - expire: if(path ~ "\.jpg$") time 360000 diff --git a/trunk/build/sae/mysae.php b/trunk/build/sae/mysae.php new file mode 100755 index 0000000000..57ce5069dd --- /dev/null +++ b/trunk/build/sae/mysae.php @@ -0,0 +1,31 @@ +installed = false; +$config->debug = false; +$config->requestType = 'PATH_INFO'; + +$config->db->host = SAE_MYSQL_HOST_M; +$config->db->slaveHost = SAE_MYSQL_HOST_S; +$config->db->port = SAE_MYSQL_PORT; +$config->db->name = SAE_MYSQL_DB; +$config->db->user = SAE_MYSQL_USER; +$config->db->password = SAE_MYSQL_PASS; +$config->db->prefix = 'zt_'; +$config->db->checkCentOS = false; + +$config->slaveDB->host = SAE_MYSQL_HOST_S; +$config->slaveDB->port = SAE_MYSQL_PORT; +$config->slaveDB->name = SAE_MYSQL_DB; +$config->slaveDB->user = SAE_MYSQL_USER; +$config->slaveDB->password = SAE_MYSQL_PASS; +$config->slaveDB->checkCentOS = false; + +$config->webRoot = '/'; +$config->default->domain = $_SERVER['HTTP_HOST']; +$config->default->lang = 'zh-cn'; + +$config->sae->storage->domain = 'zentao'; + +$saeDB = new saemysql(); +$saeSQL = "SELECT COUNT(`id`) FROM `" . $config->db->prefix . "config` WHERE 1 LIMIT 0,10"; +if($saeDB->getData($saeSQL)) $config->installed = true; +$saeDB->closeDb(); diff --git a/trunk/build/sae/mysyun.php b/trunk/build/sae/mysyun.php new file mode 100755 index 0000000000..8f5cef105a --- /dev/null +++ b/trunk/build/sae/mysyun.php @@ -0,0 +1,28 @@ +webRoot . 'index.php', file_get_contents($sourceHtaccessFile)); + file_put_contents($targetHtaccessFile, $targetHtaccessCode); +} + +$tmpRoot = $appRoot . '/tmp/'; +if(!is_dir($tmpRoot)) +{ + mkdir($tmpRoot, 0777); + mkdir($tmpRoot . 'cache', 0777); + mkdir($tmpRoot . 'extension', 0777); + mkdir($tmpRoot . 'log', 0777); + mkdir($tmpRoot . 'model', 0777); + mkdir($tmpRoot . 'svn', 0777); +} + +$dataRoot = $appRoot . 'data/'; +if(!is_dir($dataRoot)) +{ + mkdir($dataRoot, 0777); + mkdir($dataRoot . 'upload', 0777); +} diff --git a/trunk/build/sae/sae_app_wizard.xml b/trunk/build/sae/sae_app_wizard.xml new file mode 100755 index 0000000000..95bc3c794c --- /dev/null +++ b/trunk/build/sae/sae_app_wizard.xml @@ -0,0 +1,47 @@ + + + 禅道项目管理软件SAE版本 + 青岛易软天创网络科技有限公司 + default_app_icon + http://www.zentao.net/ + + 禅道项目管理软件(ZenTaoPMS)是一款国产的,基于LGPL协议,开源免费的项目管理软件,它集产品管理、项目管理、测试管理于一体,同时还包含了事务管理、组织管理等诸多功能,是中小型企业项目管理的首选。禅道项目管理软件使用PHP + MySQL开发,基于自主的PHP开发框架──ZenTaoPHP而成。第三方开发者或者企业可以非常方便的开发插件或者进行定制。

    + ]]> +
    +
    + + + Storage + Memcache + Mysql + + + + > +version: <> +cron: + - description: cron + url: project-computeburn-admin.html + schedule: every day of month 00:00 + timezone: Beijing +handle: + - rewrite: if(!is_dir() && !is_file()) goto "index.php/%{REQUEST_URI}" + - compress: if(out_header["Content-Type"] ~ "text") compress + - compress: if(out_header["Content-Type"] ~ "javascript") compress + - expire: if(path ~ "\.js$") time 360000 + - expire: if(path ~ "\.css$") time 360000 + - expire: if(path ~ "\.gif$") time 360000 + - expire: if(path ~ "\.png$") time 360000 + - expire: if(path ~ "\.ico$") time 360000 + - expire: if(path ~ "\.jpg$") time 360000 + ]]> + + + + + index.php + +
    + diff --git a/trunk/build/sae/savesql.php b/trunk/build/sae/savesql.php new file mode 100644 index 0000000000..b3e240cc39 --- /dev/null +++ b/trunk/build/sae/savesql.php @@ -0,0 +1,8 @@ +function saveSQL4SAE() +{ + if(!class_exists('dao')) return; + global $app; + $log = date('Ymd H:i:s') . ": " . $app->getURI() . "; "; + foreach(dao::$querys as $query) $log .= $query . "; "; + sae_debug($log); +} diff --git a/trunk/build/windows/uniserver/home/admin/www/includes/lang/Chinese.php b/trunk/build/windows/uniserver/home/admin/www/includes/lang/Chinese.php new file mode 100644 index 0000000000..0eaac58ef9 --- /dev/null +++ b/trunk/build/windows/uniserver/home/admin/www/includes/lang/Chinese.php @@ -0,0 +1,411 @@ + 'Uniform Server', + 'apanel' => '管理面板', + 'dev-team' => 'The Uniform Server Development Team', + + //-------------------------------------------------------------------------------------------- + // Source Code + //-------------------------------------------------------------------------------------------- + + 'code-show' => '查看源代码', + 'code-source' => '查看源代码', + 'code-back' => '关闭查看', + + //-------------------------------------------------------------------------------------------- + // Navigation + //-------------------------------------------------------------------------------------------- + + // Basic + 'nav-home' => '主页', + 'nav-web' => 'Uniform Server Website', + 'nav-secure' => '服务器安全性', + 'nav-phpinfo' => 'PHP信息', + 'nav-cgienv' => 'Perl环境', + 'nav-status' => 'Apache运行状态', + 'nav-info' => 'Apache信息', + 'nav-update' => '检查更新', + // Server Control + 'nav-start' => '服务器控制面板', + // Server Control - Service + 'nav-uservers' => '卸载服务', + 'nav-rapaches' => '重新启动Apache服务', + 'nav-rmysqls' => '重新启动MySQL服务', + // Server Control - Standard Program + 'nav-sserver' => '关闭所有服务', + 'nav-rmysql' => '运行MySQL', + 'nav-smysql' => '关闭MySQL', + // 配置s + 'nav-config' => '配置', + 'nav-aconfig' => 'Apache 配置', + 'nav-pconfig' => 'PHP 配置', + 'nav-vhost' => '管理虚拟机', + 'nav-apsetup' => '管理面板配置', + 'nav-psetup' => 'Private Server 配置', + 'nav-sslpsetup' => 'Private Secure Server Config', + 'nav-mqsetup' => 'MySQL Server 配置', + + // Tools Navigation + 'nav-tools' => '工具', + 'nav-pma' => 'phpMyAdmin', + 'nav-elog' => '错误日志查看器', + 'nav-u2w' => 'windows到unix转换器', + 'nav-smig' => 'Server Migration', + 'nav-key' => '服务器私钥和证书生成', + 'nav-mysqlrestore' => '重置MySQL密码', + + // Plugins Navigation + 'nav-plugins' => '插件管理', + 'nav-pear' => '安装Pear', + 'nav-eaccelerator' => 'eAccelerator控制面板', + // Misc Navigation + 'nav-misc' => '其他杂项', + 'nav-sup' => '在线支持', + // Documentation + 'nav-docs1' => '文档', + 'nav-sdoc' => '本机文档', + 'nav-docs2' => '在线文档', + 'nav-udoc' => '用户指南', + 'nav-wiki' => 'WIKI', + 'nav-phdoc' => 'PHP文档', + 'nav-mydoc' => 'MySQL文档', + 'nav-pedoc' => 'Perl文档', + // Languages + 'nav-langs' => 'Languages', + + //-------------------------------------------------------------------------------------------- + // Home + //-------------------------------------------------------------------------------------------- + + 'main-head' => '管理面板'. $us_apanel_version .'', + 'main-text' => ' + 欢迎使用 Uniform Server '. $us_version .'!.
    + 您现在访问的是管理面板,通过它你可以控制你的Apache、PHP、MySQL服务。虽然我们一直持续的为它增加新的功能,改进,并修复Bug,但它是稳定并且功能完整的,使用起来也非常的简单友好。 +
    + 如果发现了Bug,请反馈给我们:forum. +
    +
    + 鸣谢: +
    + Uniform Server开发团队', + 'main-secure' => '系统安全检查列表', + 'main-text-0' => '修改管理面板的用户名和密码点击这里', + 'main-text-1' => 'Change the username/password for the server 点击这里', + 'main-text-2' => 'Change the root password for mysql by editing 点击这里', + 'main-text-3' => 'Run the Security Console and see if everything is OK.', + 'main-text-4' => 'Change the username/password for the SSL server 点击这里', + + //-------------------------------------------------------------------------------------------- + // Update + //-------------------------------------------------------------------------------------------- + + 'update-head' => 'Uniform Server Version Check', + 'update-check' => 'Checking Version...', + 'update-notfound' => ' + Version file could not be found on the Uniform Server! +
    + Or +
    + The Unifrom Server is off-line! +
    + Or +
    + You are not connected to the Internet! +
    ', + + 'update-true' => ' + Installed version of the Uniform Server is the latest one. +
    + You don\'t need to update it. +
    ', + 'update-false' => 'A Newer version of the Uniform Server is available!', + 'update-new' => 'New Version', + 'update-yours' => 'Installed Version', + 'update-get' => 'You can get the newer version from our website by clicking the link below.', + + //-------------------------------------------------------------------------------------------- + // Server Control - Standard program + //-------------------------------------------------------------------------------------------- + + 'server-stop-head1' => 'Stop Servers', + 'server-stop-head2' => 'Stopping servers', + 'server-stop-txt1' => 'This script will stop Apache and MySQL servers', + 'server-stop-txt2' => 'The Servers are stopping please wait for a beep to confirm!', + 'server-stop-txt3' => 'Thank you for using The Uniform Server.', + 'server-confirm-button' => 'Confirm', + + 'start-mysql-head1' => 'Start MySQL Server', + 'start-mysql-head2' => 'Starting the MySQL server.', + 'start-mysql-txt1' => 'This script will start the MySQL server.', + 'start-mysql-txt2' => 'MySQL server already running.', + 'start-mysql-txt3' => 'The MySQL server was started you can continue using the server.', + 'start-mysql-button' => 'Start MySQL Server', + + 'stop-mysql-head1' => 'Stop MySQL Server', + 'stop-mysql-head2' => 'Stopping the MySQL server.', + 'stop-mysql-txt1' => 'This script will stop the MySQL server.', + 'stop-mysql-txt2' => 'The MySQL server was stopped.', + 'stop-mysql-txt3' => 'MySQL server not running.', + 'stop-mysql-button' => 'Stop MySQL Server', + + //-------------------------------------------------------------------------------------------- + // Server Control - Services + //-------------------------------------------------------------------------------------------- + + 'service-apache-head1' => 'Restart Apache Service', + 'service-apache-head2' => 'Restarting Apache service', + 'service-apache-txt1' => 'This script will restart the Apache service.', + 'service-apache-txt2' => 'It will take a some time', + 'service-apache-txt3' => 'The Apache service is restarting please wait
    Between 2-10 seconds!' , + 'service-apache-txt4' => 'Apanel will reload to reflect any server configuration changes.', + + 'service-mysql-head1' => 'Restart MySQL Service', + 'service-mysql-head2' => 'The MySQL service was restarted.', + 'service-mysql-txt1' => 'This script will restart the MySQL service.', + 'service-mysql-txt2' => 'It will take some time', + 'service-mysql-txt3' => 'The MySQL service was restarted you can continue using the server.', + + 'service-confirm-button' => 'Confirm', + + //-------------------------------------------------------------------------------------------- + // Apache 配置 + //-------------------------------------------------------------------------------------------- + + 'aconfig-head' => 'Apache 配置', + 'aconfig-conf' => 'Configure Apache', + 'aconfig-sname' => 'Server Name', + 'aconfig-wemail' => 'Server Admin Email', + 'aconfig-difiles' => 'Directory Index Files', + 'aconfig-ssi' => 'Server Side Includes', + 'aconfig-ssig' => 'Server Signature', + 'aconfig-listen' => 'Listen', + 'aconfig-text-0' => 'something', + 'aconfig-text-1' => ' + The changes have been successfully saved.
    Changes will take effect after server restart!', + 'aconfig-save' => 'Save', + 'aconfig-module' => 'At the moment PHP is loaded as Apache module.', + 'aconfig-cgi' => 'At the moment PHP scripts are executed though Apache CGI interface.', + 'aconfig-help' => '?', + + //-------------------------------------------------------------------------------------------- + // PHP 配置 + //-------------------------------------------------------------------------------------------- + + 'pconfig-head' => 'PHP 配置', + 'pconfig-conf' => 'Configure PHP', + 'pconfig-smode' => 'Safe Mode', + 'pconfig-rg' => 'Register Globals', + 'pconfig-mtexec' => 'Maximum Script Execute Time (s.)', + 'pconfig-mmexec' => 'Maximum Memory Amount (MB)', + 'pconfig-ssig' => 'Show PHP In Server Signature', + 'pconfig-perror' => 'Print Errors', + 'pconfig-mpsize' => 'Maximum Post Size', + 'pconfig-musize' => 'Maximum Upload Size', + 'pconfig-text-0' => 'something', + 'pconfig-text-1' => ' + The changes have been successfully saved.
    Changes will take effect after server restart!', + 'pconfig-save' => 'Save', + 'pconfig-module' => 'At the moment PHP is loaded as Apache module.', + 'pconfig-cgi' => 'At the moment PHP scripts are executed though Apache CGI interface.', + 'pconfig-help' => '?', + + //-------------------------------------------------------------------------------------------- + // VHost Manager + //-------------------------------------------------------------------------------------------- + + 'vhost-head' => 'Virtual Host', + 'vhost-setup' => 'Virtual Host Setup', + 'vhost-settings' => 'Virtual Host Settings', + 'vhost-text-0' => 'You have', + 'vhost-text-1' => 'hosts in your httpd.conf file:', + 'vhost-text-2' => 'Error in hosts file:', + 'vhost-text-3' => 'All hostnames exist in hosts file!', + 'vhost-new' => ' + Use this new and cool tool to add more virtual hosts to your server without having to edit + the httpd.conf file yourself.', + 'vhost-new-ex' => '(ex. newhost.localhost)', + 'vhost-name' => 'Name:', + 'vhost-path' => 'Path to DocumentRoot:', + 'vhost-path-ex' => '(ex. c:/www/newhost)', + 'vhost-opt' => 'Optional additions:', + 'vhost-opt-ex' => '(ex. error_log etc.)', + 'vhost-dne' => 'does not exist', + 'vhost-make' => 'Create VHost', + 'vhost-error-1' => 'Error in path to your hosts-file!', + 'vhost-error-2' => 'Error in path to your httpd.conf!', + 'vhost-text-4' => 'Safe_mode is On, so restart Apache manually!', + 'vhost-credit' => 'Script By Sukos', + + //-------------------------------------------------------------------------------------------- + // Error Log Viewer + //-------------------------------------------------------------------------------------------- + + 'elog-viewer-head1' => 'Error Log Viewer', + 'elog-viewer-head2' => 'Viewing Error Log File', + + //-------------------------------------------------------------------------------------------- + // Win to Unix Converter + //-------------------------------------------------------------------------------------------- + + 'w2u-head1' => 'Windows to Unix Converter', + 'w2u-head2' => 'Convert Windows Perl Files', + 'w2u-head3' => 'Converted Windows Perl Files', + + 'w2u-txt1' => 'If you have problems executing your cgi scripts on Unix.
    + This program will convert cgi scripts from Windows to Unix format Dec(#10#13=>#13).
    Hex 0D0A to 0A', + + 'w2u-txt2' => 'Instruction:
    After execution you can pick up scripts ready for execution + on a Unix machine from the \\cgi-bin-unix\\ directory.
    ', + + 'w2u-txt3' => 'Files converted:
    They are located in folder \\cgi-bin-unix\\ ', + + 'w2u-convert-button' => 'Convert', + + + //-------------------------------------------------------------------------------------------- + // Server Certificate and Key Generation + //-------------------------------------------------------------------------------------------- + + 'cert-head1' => 'Server Certificate and Key Generation', + 'cert-head2' => 'Verify Generation', + 'cert-head3' => 'Unable to run Certificate and Key Generation.', + 'cert-head4' => 'Certificate and Key Generation Complete.', + + 'cert-txt1' => 'Click on Generate! and follow instructions.', + 'cert-txt2' => 'Services are not allowed to interact with the desktop.
    You need to run this script manually:', + 'cert-txt3' => 'Alternatively use UniTray.', + 'cert-txt4' => 'Cirtificate location:', + 'cert-txt5' => 'Key location:', + + 'cert-confirm-button' => 'Generate', + + //-------------------------------------------------------------------------------------------- + // MySQL restore password + //-------------------------------------------------------------------------------------------- + + 'mysql-head1' => 'MySQL restore password', + 'mysql-head2' => 'Verify Restore', + 'mysql-head3' => 'MySQL password restored.', + + 'mysql-txt1' => 'Click on Restore! Restore will take several seconds.', + 'mysql-txt2' => 'Password restored you can continue using the server..', + + 'mysql-confirm-button' => 'Restore MySQL Password', + + //-------------------------------------------------------------------------------------------- + // Server Security Console + //-------------------------------------------------------------------------------------------- + + 'secure1-head' => 'Security Alert!', + 'secure1-sub' => 'Possible Attack', + 'secure1-text-0' => 'IP ADDRESS is not 127.0.0.1, but', + 'secure1-text-1' => 'Note: HTTP_REFERER is', + 'secure1-text-2' => 'To disable this warning set $unisecure to 0 in: /home/admin/www/includes/config.inc.php', + + //-------------------------------------------------------------------------------------------- + // Admin Panel Setup + //-------------------------------------------------------------------------------------------- + + 'apsetup-head' => 'Admin Panel 配置', + 'apsetup-sub-0' => 'User Management', + 'apsetup-text-0' => 'Setup the username and password for the Admin Panel here. Please note that you might have + to activate this feature in the /home/admin/www/.htaccess file.', + 'apsetup-user' => 'Username', + 'apsetup-pass' => 'Password', + 'apsetup-change' => 'Change', + 'apsetup-success' => 'The Admin Panel username/password has been changed to the new values:', + + //-------------------------------------------------------------------------------------------- + // Private Server Setup + //-------------------------------------------------------------------------------------------- + + 'psetup-head' => 'Private Server 配置', + 'psetup-sub-0' => 'User Management', + 'psetup-text-0' => 'Setup the username and password for your Private Server here. Please note that you might have + to activate this feature in the /www/.htaccess file.', + 'psetup-user' => 'Username', + 'psetup-pass' => 'Password', + 'psetup-change' => 'Change', + 'psetup-success' => 'Your Private Server username/password has been changed to the new values:', + + //-------------------------------------------------------------------------------------------- + // Private Secure Server Setup (SSL) + //-------------------------------------------------------------------------------------------- + + 'sslpsetup-head' => 'Private Secure Server 配置 (SSL)', + 'sslpsetup-sub-0' => 'User Management', + 'sslpsetup-text-0' => 'Setup the username and password for your Private Secure Server here. Please note that you might have + to activate this feature in the /ssl/.htaccess file.', + 'sslpsetup-user' => 'Username', + 'sslpsetup-pass' => 'Password', + 'sslpsetup-change' => 'Change', + 'sslpsetup-success' => 'Your Private Secure Server username/password has been changed to the new values:', + + //-------------------------------------------------------------------------------------------- + // MySQL Setup + //-------------------------------------------------------------------------------------------- + + 'mqsetup-head' => 'MySQL Server 配置', + 'mqsetup-sub-0' => 'User Management', + 'mqsetup-text-0' => 'Setup the MySQL password here. After changing the MySQL password, please note that you + must shutdown the server using the Stop.bat file and then start the server over again.', + 'mqsetup-pass' => 'MySQL Password', + 'mqsetup-change' => 'Change', + 'mqsetup-success' => 'Your MySQL password has been changed to the new value:', + + //-------------------------------------------------------------------------------------------- + // Server Security Center + //-------------------------------------------------------------------------------------------- + + 'secure-head' => 'Security Center', + 'secure-sub-0' => 'User Management Security', + 'secure-sub-1' => 'Server Security', + 'secure-text-0' => 'This part of the security center will check all user management settings to make sure that + everything is set. It will tell you if something needs to be changed.', + 'secure-text-1' => 'SECURITY MSG', + 'secure-text-2' => 'STATUS', + 'secure-text-3' => 'Admin Panel', + 'secure-text-X' => 'If the username/password is still set to root, then you probably need to change this + by clicking the UNSECURE link.', + 'secure-text-sslX' => 'Unsecure indicates you do not have a server certificate or key. Create new ones by clicking the UNSECURE link.', + 'secure-secure' => 'SECURE', + 'secure-unsecure' => 'UNSECURE', + 'secure-text-7' => 'If the password is still set to root, then you probably need to change this by clicking the UNSECURE link.', + 'secure-text-8' => 'This part of the security center will check and make sure the server settings are appropriate and set corectly.', + 'secure-text-9' => 'PHP Safe Mode', + 'secure-text-10' => 'This checks to see if PHP is running in SAFE MODE. Now, PHP does not have to run in SAFE MODE, but + if you want the extra security, you can set it by clicking on the UNSECURE link.', + 'secure-text-p' => 'Personal Server', + 'secure-text-sslp' => 'Personal Secure Server (SSL)', + 'secure-text-sslcertp' => 'Server Certificate and Key (SSL)', + 'secure-text-s' => 'MySQL Server', + 'secure-text-11' => 'Admin Panel Access', + 'secure-text-12' => 'Server Access', + 'secure-text-12ssl' => 'Server Access (SSL)', + 'secure-text-13' => 'While this is another feature that is not throughly important as other features are in place against + outside access to the Admin Panel, this checks to see if your Admin Panel is secured using the Auth method. Please change this + by editing the '.$us_apanel.'/.htaccess file.', + 'secure-text-14' => 'If you are running your server in Production Mode, Skip this one. If not and you would like to + add more security to the server by blocking it using the Auth method, then change this in by editing the '.$us_www.'/.htaccess file.', + 'secure-text-14ssl' => 'If you are running your server in Production Mode, Skip this one. If not and you would like to + add more security to the server by blocking it using the Auth method, then change this in by editing the '.$us_ssl.'/.htaccess file.', + 'secure-view' => 'Local View', + 'secure-look' => 'Due to the fact that some PC\'s have a different hostname set rather than localhost, we use the IP method here. This + checks to make sure that you are viewing the Admin Panel (this) from local.', +); + +# Beta Feature, Currently For Debugging Only +#array2table($US, true); +?> diff --git a/trunk/build/windows/uniserver/home/zentao/www/.ztaccess b/trunk/build/windows/uniserver/home/zentao/www/.ztaccess new file mode 100644 index 0000000000..9e9b9889bd --- /dev/null +++ b/trunk/build/windows/uniserver/home/zentao/www/.ztaccess @@ -0,0 +1,7 @@ +Options +FollowSymLinks +SymLinksIfOwnerMatch + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule (.*)$ /zentao/index.php/$1 [L] + diff --git a/trunk/build/windows/uniserver/readme.txt b/trunk/build/windows/uniserver/readme.txt new file mode 100644 index 0000000000..30bdf735e9 --- /dev/null +++ b/trunk/build/windows/uniserver/readme.txt @@ -0,0 +1,14 @@ +使用注意事项: + +1. 不能解压缩到含有中文和空格的路径中。 +2. 启动: + 2.1 双击运行start.exe。 软件会有一个提示,然后缩放到桌面的右下角,为一个蓝色的图标。 + 2.2 左键单击该图标,然后选择第一个菜单,"启动Apache和MySQL进程"。 + 2.3 如果想开机自动运行,则可以选择“将apache和mysql安装为服务运行”。 +3. 访问: http://localhost/zentao/ 管理用户:admin,密码 123456 + 如果你下载的88端口的包,请使用http://localhost:88/zentao来访问。 +4. 数据库的用户名和密码为:root root +5. apache的配置文件在:usr/local/apache2/conf/httpd.conf +6. mysql的数据库文件在:usr/local/mysql/data/zentao +7. zentao的程序文件在:home/zentao/ +8. 如何备份:将整个禅道目录备份即可。 diff --git a/trunk/build/windows/uniserver/unicon/tray_menu/UniTray1.ini b/trunk/build/windows/uniserver/unicon/tray_menu/UniTray1.ini new file mode 100644 index 0000000000..f02e79e9e8 --- /dev/null +++ b/trunk/build/windows/uniserver/unicon/tray_menu/UniTray1.ini @@ -0,0 +1,165 @@ +[Config] +ID=UniTrayController1 +ImageList=menu_icons.dat +ServiceGlyphRunning=2 +ServiceGlyphPaused=18 +ServiceGlyphStopped=18 +;TrayIconAllRunning=9 +;TrayIconSomeRunning=10 +;TrayIconNoneRunning=11 +AboutHeader=UniTray UniServer V5-Nano +AboutVersion=V5.0 +;TrayIcon=tray_image_1.dat +TrayIcon=zentao.dat + +[AboutText] +UniTray is a small tray icon for the Uniform Server. The main system, Aestan Tray Icon was developed by Onno Broekmans using Borland Delphi for another server and as since then being licensed under the Creative Commons for Public Domain. + +UniTray packs most of the functionality that Admin Panel provides into a single Tray Icon! UniTray is fully customisable allowing you to tailor it to your own requirements. + +[Variables] +;Type: prompt; Name: PUserName; PromptCaption: "Uniform Server"; PromptText: "Please enter the name of the user whose credentials you want to change/add:"; DefaultValue: "Guest" +; The next line contains the declaration of the %USRoot% variable; it should +; point to the root directory of the Uniform Server package + +; Gets the root path as specified on the command line +Type: commandline; Name: top; ParamName: "ustop" +Type: static; Name: USRoot; Value: "%top%"; Flags: ispath +Type: static; Name: LocalRoot; Value: "%top%\usr\local" +Type: static; Name: UsrRoot; Value: "%top%\usr" +Type: static; Name: ServerRoot; Value: "%top%\www" +Type: static; Name: PHP; Value: "%top%\usr\local\php" +Type: static; Name: ServerCon; Value: "%top%\unicon" +Type: static; Name: ServerConMain; Value: "%top%\unicon\main" +Type: static; Name: UniTray; Value: "%top%\unicon\tray_menu" + +[Menu.Left.Settings] +AutoLineReduction=no +AutoHotKeys=no +BarVisible=yes +BarPictureHorzAlignment=center +BarPictureOffsetX=0 +BarPictureOffsetY=0 +BarPicturePicture=menu_image_left.dat +BarPictureTransparent=no +BarPictureVertAlignment=middle +BarBorder=clNone +BarSide=left +BarSpace=0 +BarWidth=50 +SeparatorsAlignment=center + +[Menu.Right.Settings] +AutoLineReduction=no +AutoHotKeys=no +BarVisible=yes +BarPictureHorzAlignment=center +BarPictureOffsetX=0 +BarPictureOffsetY=0 +BarPicturePicture=menu_image_right.dat +BarPictureTransparent=no +BarPictureVertAlignment=middle +BarBorder=clNone +BarSide=left +BarSpace=0 +BarWidth=50 +SeparatorsAlignment=center + +[DoubleClickAction] +Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 1"; + +;=== LEFT MENU ============ +[Menu.Left] +Type: item; Caption: "ApacheMySQL"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\start_servers.php 7";ShowCmd: hidden; Glyph: 9 +Type: item; Caption: "ֹͣApacheMySQL"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\stop_servers.php 7";ShowCmd: hidden; Glyph: 11 + +Type: separator +Type: item; Caption: "װApacheMySQLΪ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\service_install_run.php 7";ShowCmd: hidden; Glyph: 0 +Type: item; Caption: "ֹͣApacheMySQLƳ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\service_stop_uninstall.php 7";ShowCmd: hidden; Glyph: 1 + +Type: separator +Type: item; Caption: "UniServer״̬"; Action: multi; Actions: ServerStatus; Glyph: 14 +Type: submenu; Caption: "ApacheMySQL߼"; SubMenu: Individual_unicontrol; Glyph: 3 +Type: submenu; Caption: "ApacheMySQLϢ־"; SubMenu: ServerInfo; Glyph: 14 +Type: submenu; Caption: "ApacheMySQLPHP"; SubMenu: Advanced; Glyph: 7 + +Type: separator +Type: item; Caption: "ʱ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 8";ShowCmd: hidden; Glyph: 4 +Type: item; Caption: "ٷվ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 9";ShowCmd: hidden; Glyph: 4 + +Type: separator +Type: item; Caption: "ʹ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 1";ShowCmd: hidden; Glyph: 4 +Type: item; Caption: "phpMyAdmin"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 2";ShowCmd: hidden; Glyph: 4 +Type: item; Caption: "ʱҳ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 3";ShowCmd: hidden; Glyph: 4 + +Type: separator +Type: item; Caption: "ؼز˵"; Action: readconfig; Glyph: 15 +Type: item; Caption: "UniServer"; Action: run; FileName: "%ServerCon%\docs\Run.bat"; ShowCmd: hidden; Glyph: 18 +Type: item; Caption: "UniServer"; Action: about; Glyph: 14 + +Type: separator +Type: item; Caption: "˳"; Action: exit; Glyph: 16 + +[Individual_unicontrol] +Type: item; Caption: "Apache"; Action: shellexecute; FileName: "%PHP%\php-win.exe"; Parameters: " -n %ServerConMain%\start_servers.php 1"; Glyph: 9 +Type: item; Caption: "ֹͣApache"; Action: shellexecute; FileName: "%PHP%\php-win.exe"; Parameters: " -n %ServerConMain%\stop_servers.php 1"; Glyph: 11 + +Type: separator +Type: item; Caption: "MySQL"; Action: shellexecute; FileName: "%PHP%\php-win.exe"; Parameters: " -n %ServerConMain%\start_servers.php 2"; Glyph: 9 +Type: item; Caption: "ֹͣMySQL"; Action: shellexecute; FileName: "%PHP%\php-win.exe"; Parameters: " -n %ServerConMain%\stop_servers.php 2"; Glyph: 11 + +Type: separator +Type: item; Caption: "װApacheΪ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\service_install_run.php 1"; Glyph: 0 +Type: item; Caption: "ֹͣApacheƳ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\service_stop_uninstall.php 1"; Glyph: 1 + +Type: separator +Type: item; Caption: "װMySQLΪ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\service_install_run.php 2"; Glyph: 0 +Type: item; Caption: "ֹͣMySQLƳ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %ServerConMain%\service_stop_uninstall.php 2"; Glyph: 1 + +;=== RIGHT MENU ============ +[Menu.Right] +Type: item; Caption: "˳"; Action: exit; Glyph: 16 + +;=== COMMON LEFT RIGHT ======= +[Advanced] +Type: item; Caption: "Apache"; Action: multi; Actions: ApacheSyntaxCheck; Glyph: 14 +Type: item; Caption: "޸Apache"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\apache2\conf\httpd.conf"; Glyph: 7 + +Type: separator +Type: item; Caption: "ָMySQL"; Action: run; FileName: "%ServerCon%\restore_mysql_password\Run_restore.bat"; Glyph: 9 +Type: item; Caption: "޸MySQL"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\mysql\my.ini"; Glyph: 7 + +Type: separator +Type: item; Caption: "޸õphp.ini"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\php\php.ini_production_nano"; Glyph: 7 +Type: item; Caption: "޸Ŀõphp.ini"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\php\php.ini_delvelopment_nano"; Glyph: 7 + +Type: separator +Type: item; Caption: "лphp"; Action: shellexecute; FileName: "%ServerCon%\php_dev_production\Switch_production.bat"; Parameters: " pro"; Glyph: 9 +Type: item; Caption: "лphp"; Action: shellexecute; FileName: "%ServerCon%\php_dev_production\Switch_Development.bat"; Parameters: " dev"; Glyph: 9 + +;Type: separator +;Type: item; Caption: "Start Portable Cron"; Action: shellexecute; FileName: "%PHP%\php-win.exe"; Parameters: " -n %ServerConMain%\start_servers.php 16"; Glyph: 9 +;Type: item; Caption: "Stop Portable Cron"; Action: shellexecute; FileName: "%PHP%\php-win.exe"; Parameters: " -n %ServerConMain%\stop_servers.php 16"; Glyph: 11 + +; Server information and Logs +[ServerInfo] +Type: item; Caption: "ApacheϢ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 5";ShowCmd: hidden; Glyph: 4 +Type: item; Caption: "Apache״̬"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 6";ShowCmd: hidden; Glyph: 4 +Type: item; Caption: "PHPϢ"; Action: shellexecute; FileName: "%PHP%\php.exe"; Parameters: " -n %UniTray%\unitray_info.php 7";ShowCmd: hidden; Glyph: 4 + +Type: separator +Type: item; Caption: "Apache־"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\apache2\logs\error.log"; Glyph: 18 +Type: item; Caption: "Apache־"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\apache2\logs\access.log"; Glyph: 18 + +;Type: separator +;Type: item; Caption: "Apache SSL Error Log"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\apache2\logs\error_ssl.log"; Glyph: 18 +;Type: item; Caption: "Apache SSL Access Log"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\apache2\logs\access_ssl.log"; Glyph: 18 + +Type: separator +Type: item; Caption: "MySQL־"; Action: shellexecute; FileName: "%Windows%\Notepad.exe"; Parameters: "%LocalRoot%\mysql\data\mysql.err"; Glyph: 18 + +[ServerStatus] +Action: run; FileName: "%ServerConMain%\server_status.bat" + +[ApacheSyntaxCheck] +Action: run; FileName: "%ServerConMain%\apache_syntax_check.bat" diff --git a/trunk/build/windows/uniserver/unicon/tray_menu/unitray_info.php b/trunk/build/windows/uniserver/unicon/tray_menu/unitray_info.php new file mode 100644 index 0000000000..61e10c99c6 --- /dev/null +++ b/trunk/build/windows/uniserver/unicon/tray_menu/unitray_info.php @@ -0,0 +1,98 @@ + diff --git a/trunk/build/windows/uniserver/unicon/tray_menu/zentao.dat b/trunk/build/windows/uniserver/unicon/tray_menu/zentao.dat new file mode 100644 index 0000000000..7e41175181 Binary files /dev/null and b/trunk/build/windows/uniserver/unicon/tray_menu/zentao.dat differ diff --git a/trunk/build/windows/uniserver/usr/local/apache2/conf/httpd.conf b/trunk/build/windows/uniserver/usr/local/apache2/conf/httpd.conf new file mode 100644 index 0000000000..fcff890a25 --- /dev/null +++ b/trunk/build/windows/uniserver/usr/local/apache2/conf/httpd.conf @@ -0,0 +1,1037 @@ +############################################# +### Uniform Server - Apache Configuration ### +############################################# +# +# This is a neat and pre-configured setting of Apache 2 for the +# Uniform Server. +# +############################################# +### Apache Server Configuration - Notes ### +############################################# +# +# Based upon the NCSA server configuration files originally by Rob McCool. +# +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See for detailed information. +# In particular, see +# +# for a discussion of each configuration directive. +# +# Do NOT simply read the instructions in here without understanding +# what they do. They're here only as hints or reminders. If you are unsure +# consult the online docs. You have been warned. +# +# The configuration directives are grouped into three basic sections: +# 1. Directives that control the operation of the Apache server process as a +# whole (the 'global environment'). +# 2. Directives that define the parameters of the 'main' or 'default' server, +# which responds to requests that aren't handled by a virtual host. +# These directives also provide default values for the settings +# of all virtual hosts. +# 3. Settings for virtual hosts, which allow Web requests to be sent to +# different IP addresses or hostnames and have them handled by the +# same Apache server process. +# +# Configuration and logfile names: If the filenames you specify for many +# of the server's control files begin with "/" (or "drive:/" for Win32), the +# server will use that explicit path. If the filenames do *not* begin +# with "/", the value of ServerRoot is prepended -- so "logs/foo.log" +# with ServerRoot set to "C:/Program Files/Apache Software Foundation/Apache2.2" will be interpreted by the +# server as "C:/Program Files/Apache Software Foundation/Apache2.2/logs/foo.log". +# +# NOTE: Where filenames are specified, you must use forward slashes +# instead of backslashes (e.g., "c:/apache" instead of "c:\apache"). +# If a drive letter is omitted, the drive on which Apache.exe is located +# will be used by default. It is recommended that you always supply +# an explicit drive letter in absolute paths to avoid confusion. + +### Section 1: Global Environment +# +# The directives in this section affect the overall operation of Apache, +# such as the number of concurrent requests it can handle or where it +# can find its configuration files. +# + +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# Do not add a slash at the end of the directory path. If you point +# ServerRoot at a non-local disk, be sure to point the LockFile directive +# at a local disk. If you wish to share the same ServerRoot for multiple +# httpd daemons, you will need to change at least LockFile and PidFile. +# + +ServerRoot "D:/zentao/usr/local/apache2" + +# ScoreBoardFile: File used to store internal server process information. +# If unspecified (the default), the scoreboard will be stored in an +# anonymous shared memory segment, and will be unavailable to third-party +# applications. +# If specified, ensure that no two invocations of Apache share the same +# scoreboard file. The scoreboard file MUST BE STORED ON A LOCAL DISK. + +#ScoreBoardFile logs/apache_runtime_status + +# PidFile: The file in which the server should record its process +# identification number when it starts. + +PidFile logs/httpd.pid + +# Timeout: The number of seconds before receives and sends time out. + +Timeout 300 + +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. + +KeepAlive On + +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. + +MaxKeepAliveRequests 100 + +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. + +KeepAliveTimeout 15 + +## +## Server-Pool Size Regulation (MPM specific) +## + +# WinNT MPM +# ThreadsPerChild: constant number of worker threads in the server process +# MaxRequestsPerChild: maximum number of requests a server process serves + + +ThreadsPerChild 250 +MaxRequestsPerChild 0 + + +# +# Listen: Allows you to bind Apache to specific IP addresses and/or +# ports, instead of the default. See also the +# directive. +# +# Change this to Listen on specific IP addresses as shown below to +# prevent Apache from glomming onto all bound IP addresses. +# +#Listen 12.34.56.78:80 + +Listen 80 + +# +# Dynamic Shared Object (DSO) Support +# +# To be able to use the functionality of a module which was built as a DSO you +# have to place corresponding `LoadModule' lines at this location so the +# directives contained in it are actually available _before_ they are used. +# Statically compiled modules (those listed by `httpd -l') do not need +# to be loaded here. +# +# Example: +# LoadModule foo_module modules/mod_foo.so +# + +LoadModule actions_module modules/mod_actions.so +LoadModule alias_module modules/mod_alias.so +LoadModule asis_module modules/mod_asis.so +LoadModule auth_basic_module modules/mod_auth_basic.so +#LoadModule auth_digest_module modules/mod_auth_digest.so +#LoadModule authn_alias_module modules/mod_authn_alias.so +#LoadModule authn_anon_module modules/mod_authn_anon.so +#LoadModule authn_dbd_module modules/mod_authn_dbd.so +#LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_default_module modules/mod_authn_default.so +LoadModule authn_file_module modules/mod_authn_file.so +#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so +#LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_default_module modules/mod_authz_default.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_host_module modules/mod_authz_host.so +#LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule autoindex_module modules/mod_autoindex.so +#LoadModule cache_module modules/mod_cache.so +#LoadModule cern_meta_module modules/mod_cern_meta.so +LoadModule cgi_module modules/mod_cgi.so +#LoadModule charset_lite_module modules/mod_charset_lite.so +LoadModule dav_module modules/mod_dav.so +#LoadModule dav_fs_module modules/mod_dav_fs.so +#LoadModule dav_lock_module modules/mod_dav_lock.so +#LoadModule dbd_module modules/mod_dbd.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule dir_module modules/mod_dir.so +#LoadModule disk_cache_module modules/mod_disk_cache.so +#LoadModule dumpio_module modules/mod_dumpio.so +LoadModule env_module modules/mod_env.so +#LoadModule expires_module modules/mod_expires.so +#LoadModule ext_filter_module modules/mod_ext_filter.so +#LoadModule file_cache_module modules/mod_file_cache.so +#LoadModule filter_module modules/mod_filter.so +LoadModule headers_module modules/mod_headers.so +#LoadModule ident_module modules/mod_ident.so +#LoadModule imagemap_module modules/mod_imagemap.so +LoadModule include_module modules/mod_include.so +LoadModule info_module modules/mod_info.so +LoadModule isapi_module modules/mod_isapi.so +#LoadModule ldap_module modules/mod_ldap.so +#LoadModule logio_module modules/mod_logio.so +LoadModule log_config_module modules/mod_log_config.so +#LoadModule log_forensic_module modules/mod_log_forensic.so +#LoadModule mem_cache_module modules/mod_mem_cache.so +LoadModule mime_module modules/mod_mime.so +#LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule negotiation_module modules/mod_negotiation.so +#LoadModule proxy_module modules/mod_proxy.so +#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so +#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so +#LoadModule proxy_connect_module modules/mod_proxy_connect.so +#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so +#LoadModule proxy_http_module modules/mod_proxy_http.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule setenvif_module modules/mod_setenvif.so +#LoadModule speling_module modules/mod_speling.so +#LoadModule ssl_module modules/mod_ssl.so +LoadModule status_module modules/mod_status.so +#LoadModule substitute_module modules/mod_substitute.so +#LoadModule unique_id_module modules/mod_unique_id.so +LoadModule userdir_module modules/mod_userdir.so +#LoadModule usertrack_module modules/mod_usertrack.so +LoadModule version_module modules/mod_version.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so + + +# Enable dav-fs and gzip output compression. +Loadfile "D:/zentao/usr/local/php/libmysql.dll" + +# DEFLATE Module Settings + + # Add file mime types to be compressed + AddOutputFilterByType DEFLATE text/plain + AddOutputFilterByType DEFLATE text/html + AddOutputFilterByType DEFLATE text/xml + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE application/xml + AddOutputFilterByType DEFLATE application/xhtml+xml + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/javascript + AddOutputFilterByType DEFLATE application/x-javascript + + #Set compression Highest 9 - Lowest 1 + DeflateCompressionLevel 9 + + # If you must enable these lines, - obsolete browser + #BrowserMatch ^Mozilla/4 gzip-only-text/html + #BrowserMatch ^Mozilla/4\.0[678] no-gzip + #BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + + + # Setup custom deflate log - enable only for testing + # Eats CPU time hence disable after testing + DeflateFilterNote Input instream + DeflateFilterNote Output outstream + DeflateFilterNote Ratio ratio + LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%)' deflate + #CustomLog logs/deflate.log deflate + + + # Make sure proxies don't deliver the wrong content + + Header append Vary User-Agent env=!dont-vary + + + +# DEFLATE Module Settings End + + +# Mod_Dav Module Settings +# + #LoadModule dav_fs_module modules/mod_dav_fs.so + # DavLockDB D:/zentao/tmp/DavLock +# +# AuthName "The Uniform Server" +# AuthUserFile D:/zentao/htpasswd/home/admin/www/.htpasswd +# AuthType Basic +# AllowOverride None +# Dav On +# Require valid-user +# +# +# End Mod_Dav Settings + +# ExtendedStatus controls whether Apache will generate "full" status +# information (ExtendedStatus On) or just basic information (ExtendedStatus +# Off) when the "server-status" handler is called. The default is Off. + +ExtendedStatus On + +### Section 2: 'Main' server configuration +# +# The directives in this section set up the values used by the 'main' +# server, which responds to any requests that aren't handled by a +# definition. These values also provide defaults for +# any containers you may define later in the file. +# +# All of these directives may appear inside containers, +# in which case these default settings will be overridden for the +# virtual host being defined. +# + +# +# ServerAdmin: Your address, where problems with the server should be +# e-mailed. This address appears on some server-generated pages, such +# as error documents. e.g. admin@your-domain.com +# + +ServerAdmin admin@localhost + +# ServerName gives the name and port that the server uses to identify itself. +# This can often be determined automatically, but we recommend you specify +# it explicitly to prevent problems during startup. +# If your host doesn't have a registered DNS name, enter its IP address here. + +ServerName localhost:80 + +# UseCanonicalName: Determines how Apache constructs self-referencing +# URLs and the SERVER_NAME and SERVER_PORT variables. +# When set "Off", Apache will use the Hostname and Port supplied +# by the client. When set "On", Apache will use the value of the +# ServerName directive. + +UseCanonicalName Off + +# DocumentRoot: The directory out of which you will serve your +# documents. By default, all requests are taken from this directory, but +# symbolic links and aliases may be used to point to other locations. + +DocumentRoot "D:/zentao/www" + +# Each directory to which Apache has access can be configured with respect +# to which services and features are allowed and/or disabled in that +# directory (and its subdirectories). +# +# First, we configure the "default" to be a very restrictive set of +# features. + + + Options Indexes Includes + AllowOverride All + + + AddType application/x-httpd-php .phtml .php3 .php + AddType Application/x-httpd-php-source .phps + +# Using PHP as an Apache Module... + +#LoadModule php5_module "D:/zentao/usr/local/php/php5apache2.dll" +LoadModule php5_module "D:/zentao/usr/local/php/php5apache2_2.dll" + +PHPIniDir "D:/zentao/usr/local/php/" + +# Using PHP as a CGI Module +# ScriptAlias "/__php_dir__/" "D:/zentao/usr/local/php/" +# Action application/x-httpd-php "/__php_dir__/php.exe" +# +# Note that from this point forward you must specifically allow +# particular features to be enabled - so if something's not working as +# you might expect, make sure that you have specifically enabled it +# below. + + +# This should be changed to whatever you set DocumentRoot to. + + + +# Possible values for the Options directive are "None", "All", +# or any combination of: +# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews +# +# Note that "MultiViews" must be named *explicitly* --- "Options All" +# doesn't give it to you. +# +# The Options directive is both complicated and important. Please see +# http://httpd.apache.org/docs-2.0/mod/core.html#options +# for more information. + + Options Indexes Includes + +# AllowOverride controls what directives may be placed in .htaccess files. +# It can be "All", "None", or any combination of the keywords: +# Options FileInfo AuthConfig Limit + + AllowOverride All + +# Controls who can get stuff from this server. + + Order allow,deny + Allow from all + + + +Alias /apanel "D:/zentao/home/admin/www/" + + Options Indexes Includes + AllowOverride All + Order allow,deny + Allow from all + + +Alias /webalizer "D:/zentao/webalizer/" + + Options Indexes Includes + AllowOverride All + Order allow,deny + Allow from all + + +Alias /test_access "D:/zentao/home/access/www/" + + Order allow,deny + Allow from all + + +Alias /zentao "D:/zentao/home/zentao/www/" + + Options Indexes Includes + AllowOverride All + Order allow,deny + Allow from all + + + +# UserDir: The name of the directory that is appended onto a user's home +# directory if a ~user request is received. Be especially careful to use +# proper, forward slashes here. On Windows NT, "Personal/My Website" +# is a more appropriate choice. + +UserDir "D:/zentao/www/" + +# Control access to UserDir directories. The following is an example +# for a site where these directories are restricted to read-only. +# +# You must correct the path for the root to match your system's configured +# user directory location, e.g. "C:/WinNT/profiles/*/My Documents/My Website" +# or whichever, as appropriate. +# +# +# AllowOverride FileInfo AuthConfig Limit +# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec +# +# Order allow,deny +# Allow from all +# +# +# Order deny,allow +# Deny from all +# +# + +# DirectoryIndex: sets the file that Apache will serve if a directory +# is requested. +# + +DirectoryIndex index.html index.shtml index.html.var index.htm index.php3 index.php index.pl index.cgi + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. + +AccessFileName .ztaccess + +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. + + + Order allow,deny + Deny from all + + +# TypesConfig describes where the mime.types file (or equivalent) is +# to be found. + +TypesConfig conf/mime.types + +# DefaultType is the default MIME type the server will use for a document +# if it cannot otherwise determine one, such as from filename extensions. +# If your server contains mostly text or HTML documents, "text/plain" is +# a good value. If most of your content is binary, such as applications +# or images, you may want to use "application/octet-stream" instead to +# keep browsers from trying to display binary files as though they are +# text. + +DefaultType text/plain + +# The mod_mime_magic module allows the server to use various hints from the +# contents of the file itself to determine its type. The MIMEMagicFile +# directive tells the module where the hint definitions are located. + + + MIMEMagicFile conf/magic + + +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. + +HostnameLookups Off + +# EnableMMAP: Control whether memory-mapping is used to deliver +# files (assuming that the underlying OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. On some systems, turning it off (regardless of +# filesystem) can improve performance; for details, please see +# http://httpd.apache.org/docs-2.0/mod/core.html#enablemmap + +#EnableMMAP off + +# EnableSendfile: Control whether the sendfile kernel support is +# used to deliver files (assuming that the OS supports it). +# The default is on; turn this off if you serve from NFS-mounted +# filesystems. Please see +# http://httpd.apache.org/docs-2.0/mod/core.html#enablesendfile + +#EnableSendfile off + +# +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog "logs/error.log" + +# +# LogLevel: Control the number of messages logged to the error_log. +# Possible values include: debug, info, notice, warn, error, crit, +# alert, emerg. +# +LogLevel warn + +# The following directives define some format nicknames for use with +# a CustomLog directive (see below). + +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %b" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# You need to enable mod_logio.c to use %I and %O +#LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio + +# The location and format of the access logfile (Common Logfile Format). +# If you do not define any access logfiles within a +# container, they will be logged here. Contrariwise, if you *do* +# define per- access logfiles, transactions will be +# logged therein and *not* in this file. + +CustomLog logs/access.log combined + +# If you would like to have agent and referer logfiles, uncomment the +# following directives. + +#CustomLog logs/referer.log referer +#CustomLog logs/agent.log agent + +# If you prefer a single logfile with access, agent, and referer information +# (Combined Logfile Format) you can use the following directive. + +# ServerTokens +# This directive configures what you return as the Server HTTP response +# Header. The default is 'Full' which sends information about the OS-Type +# and compiled in modules. +# Set to one of: Full | OS | Minor | Minimal | Major | Prod +# where Full conveys the most information, and Prod the least. + +ServerTokens Full + +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail + +ServerSignature Off + +#Aliases: Add here as many aliases as you need (with no limit). The format is +# Alias fakename realname +# +# Note that if you include a trailing / on fakename then the server will +# require it to be present in the URL. So "/icons" isn't aliased in this +# example, only "/icons/". If the fakename is slash-terminated, then the +# realname must also be slash terminated, and if the fakename omits the +# trailing slash, the realname must also omit it. +# +# We include the /icons/ alias for FancyIndexed directory listings. If you +# do not use FancyIndexing, you may comment this out. + +Alias /icons/ "D:/zentao/usr/local/apache2/icons/" + + Options Indexes MultiViews + AllowOverride None + Options None + Order allow,deny + Allow from all + + +Alias /docs "D:/zentao/docs/" + + Options Indexes Includes + AllowOverride All + Order allow,deny + Allow from all + + +# ScriptAlias: This controls which directories contain server scripts. +# ScriptAliases are essentially the same as Aliases, except that +# documents in the realname directory are treated as applications and +# run by the server when requested rather than as documents sent to the client. +# The same rules about trailing "/" apply to ScriptAlias directives as to +# Alias. + +ScriptAlias /cgi-bin "D:/zentao/cgi-bin/" + +# "C:/Program Files/Apache Group/Apache2/cgi-bin" should be changed to whatever your ScriptAliased +# CGI directory exists, if you have that configured. + + + AllowOverride All + Options ExecCGI + + + + AllowOverride All + Options ExecCGI + + +# Redirect allows you to tell clients about documents which used to exist in +# your server's namespace, but do not anymore. This allows you to tell the +# clients where to look for the relocated document. +# Example: +# Redirect permanent /foo http:/D:/zentao/www.example.com/bar + +Redirect permanent /a http://localhost/apanel/ +Redirect permanent /a-cgi http://localhost/apanel/cgi-bin/ + +# Directives controlling the display of server-generated directory listings. + +# IndexOptions: Controls the appearance of server-generated directory +# listings. + +IndexOptions FancyIndexing VersionSort + +# AddIcon* directives tell the server which icon to show for different +# files or filename extensions. These are only displayed for +# FancyIndexed directories. + +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip + +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (VID,/icons/movie.gif) video/* + +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/tar.gif .tar +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/text.gif .txt +AddIcon /icons/c.gif .c +AddIcon /icons/p.gif .pl .py +AddIcon /icons/f.gif .for +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tex.gif .tex +AddIcon /icons/bomb.gif core + +AddIcon /icons/back.gif .. +AddIcon /icons/hand.right.gif README +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/blank.gif ^^BLANKICON^^ + +# DefaultIcon is which icon to show for files which do not have an icon +# explicitly set. + +DefaultIcon /icons/unknown.gif + +# AddDescription allows you to place a short description after a file in +# server-generated indexes. These are only displayed for FancyIndexed +# directories. +# Format: AddDescription "description" filename + +#AddDescription "GZIP compressed document" .gz +#AddDescription "tar archive" .tar +#AddDescription "GZIP compressed tar archive" .tgz + +# ReadmeName is the name of the README file the server will look for by +# default, and append to directory listings. +# +# HeaderName is the name of a file which should be prepended to +# directory indexes. + +ReadmeName README.html +HeaderName HEADER.html + +# IndexIgnore is a set of filenames which directory indexing should ignore +# and not include in the listing. Shell-style wildcarding is permitted. + +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t + +# AddEncoding allows you to have certain browsers (Mosaic/X 2.1+) uncompress +# information on the fly. Note: Not all browsers support this. +# Despite the name similarity, the following Add* directives have nothing +# to do with the FancyIndexing customization directives above. + +AddEncoding x-compress Z +AddEncoding x-gzip gz tgz + +# DefaultLanguage and AddLanguage allows you to specify the language of +# a document. You can then use content negotiation to give a browser a +# file in a language the user can understand. +# +# Specify a default language. This means that all data +# going out without a specific language tag (see below) will +# be marked with this one. You probably do NOT want to set +# this unless you are sure it is correct for all cases. +# +# * It is generally better to not mark a page as +# * being a certain language than marking it with the wrong +# * language! +# +# DefaultLanguage nl +# +# Note 1: The suffix does not have to be the same as the language +# keyword --- those with documents in Polish (whose net-standard +# language code is pl) may wish to use "AddLanguage pl .po" to +# avoid the ambiguity with the common suffix for perl scripts. +# +# Note 2: The example entries below illustrate that in some cases +# the two character 'Language' abbreviation is not identical to +# the two character 'Country' code for its country, +# E.g. 'Danmark/dk' versus 'Danish/da'. +# +# Note 3: In the case of 'ltz' we violate the RFC by using a three char +# specifier. There is 'work in progress' to fix this and get +# the reference data for rfc1766 cleaned up. +# +# Danish (da) - Dutch (nl) - English (en) - Estonian (et) +# French (fr) - German (de) - Greek-Modern (el) +# Italian (it) - Norwegian (no) - Norwegian Nynorsk (nn) - Korean (ko) +# Portugese (pt) - Luxembourgeois* (ltz) +# Spanish (es) - Swedish (sv) - Catalan (ca) - Czech(cs) +# Polish (pl) - Brazilian Portuguese (pt-br) - Japanese (ja) +# Russian (ru) - Croatian (hr) + +AddLanguage da .dk +AddLanguage nl .nl +AddLanguage en .en +AddLanguage et .et +AddLanguage fr .fr +AddLanguage de .de +AddLanguage he .he +AddLanguage el .el +AddLanguage it .it +AddLanguage ja .ja +AddLanguage pl .po +AddLanguage ko .ko +AddLanguage pt .pt +AddLanguage nn .nn +AddLanguage no .no +AddLanguage pt-br .pt-br +AddLanguage ltz .ltz +AddLanguage ca .ca +AddLanguage es .es +AddLanguage sv .sv +AddLanguage cs .cz .cs +AddLanguage ru .ru +AddLanguage zh-CN .zh-cn +AddLanguage zh-TW .zh-tw +AddLanguage hr .hr + +# LanguagePriority allows you to give precedence to some languages +# in case of a tie during content negotiation. +# +# Just list the languages in decreasing order of preference. We have +# more or less alphabetized them here. You probably want to change this. + +LanguagePriority en da nl et fr de el it ja ko no pl pt pt-br ltz ca es sv tw + +# ForceLanguagePriority allows you to serve a result page rather than +# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) +# [in case no accepted languages matched the available variants] + +ForceLanguagePriority Prefer Fallback + +# Specify a default charset for all pages sent out. This is +# always a good idea and opens the door for future internationalisation +# of your web site, should you ever want it. Specifying it as +# a default does little harm; as the standard dictates that a page +# is in iso-8859-1 (latin1) unless specified otherwise i.e. you +# are merely stating the obvious. There are also some security +# reasons in browsers, related to javascript and URL parsing +# which encourage you to always set a default char set. + +#AddDefaultCharset ISO-8859-1 +#AddDefaultCharset UTF-8 + +# Commonly used filename extensions to character sets. You probably +# want to avoid clashes with the language extensions, unless you +# are good at carefully testing your setup after each change. +# See http:/D:/zentao/www.iana.org/assignments/character-sets for the +# official list of charset names and their respective RFCs. + +AddCharset ISO-8859-1 .iso8859-1 .latin1 +AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen +AddCharset ISO-8859-3 .iso8859-3 .latin3 +AddCharset ISO-8859-4 .iso8859-4 .latin4 +AddCharset ISO-8859-5 .iso8859-5 .latin5 .cyr .iso-ru +AddCharset ISO-8859-6 .iso8859-6 .latin6 .arb +AddCharset ISO-8859-7 .iso8859-7 .latin7 .grk +AddCharset ISO-8859-8 .iso8859-8 .latin8 .heb +AddCharset ISO-8859-9 .iso8859-9 .latin9 .trk +AddCharset ISO-2022-JP .iso2022-jp .jis +AddCharset ISO-2022-KR .iso2022-kr .kis +AddCharset ISO-2022-CN .iso2022-cn .cis +AddCharset Big5 .Big5 .big5 +# For russian, more than one charset is used (depends on client, mostly): +AddCharset WINDOWS-1251 .cp-1251 .win-1251 +AddCharset CP866 .cp866 +AddCharset KOI8-r .koi8-r .koi8-ru +AddCharset KOI8-ru .koi8-uk .ua +AddCharset ISO-10646-UCS-2 .ucs2 +AddCharset ISO-10646-UCS-4 .ucs4 +AddCharset UTF-8 .utf8 + +# The set below does not map to a specific (iso) standard +# but works on a fairly wide range of browsers. Note that +# capitalization actually matters (it should not, but it +# does for some browsers). +# +# See http:/D:/zentao/www.iana.org/assignments/character-sets +# for a list of sorts. But browsers support few. + +AddCharset GB2312 .gb2312 .gb +AddCharset utf-7 .utf7 +AddCharset utf-8 .utf8 +AddCharset big5 .big5 .b5 +AddCharset EUC-TW .euc-tw +AddCharset EUC-JP .euc-jp +AddCharset EUC-KR .euc-kr +AddCharset shift_jis .sjis + +# AddType allows you to add to or override the MIME configuration +# file mime.types for specific file types. + +AddType application/x-tar .tgz +AddType image/x-icon .ico + +# AddHandler allows you to map certain file extensions to "handlers": +# actions unrelated to filetype. These can be either built into the server +# or added with the Action directive (see below) +# +# To use CGI scripts outside of ScriptAliased directories: +# (You will also need to add "ExecCGI" to the "Options" directive.) + +#AddHandler cgi-script .bat .exe .pl .cgi + +#Action application/x-perl "D:/zentao/usr/bin/perl" + +# For files that include their own HTTP headers: + +#AddHandler send-as-is asis + +# For server-parsed imagemap files: + +#AddHandler imap-file map + +AddHandler server-parsed .shtml .shtm .sht + +# For type maps (negotiated resources): +# (This is enabled by default to allow the Apache "It Worked" page +# to be distributed in multiple languages.) + +AddHandler type-map var + +# Filters allow you to process content before it is sent to the client. +# +# To parse .shtml files for server-side includes (SSI): +# (You will also need to add "Includes" to the "Options" directive.) + +AddType text/html .shtml +AddOutputFilter INCLUDES .shtml + +# Action lets you define media types that will execute a script whenever +# a matching file is called. This eliminates the need for repeated URL +# pathnames for oft-used CGI file processors. +# Format: Action media/type /cgi-script/location +# Format: Action handler-name /cgi-script/location + + +# Customizable error responses come in three flavors: +# 1) plain text 2) local redirects 3) external redirects +# +# Some examples: +#ErrorDocument 500 "The server made a boo boo." +#ErrorDocument 404 /missing.html +#ErrorDocument 404 "D:/zentao/cgi-bin/missing_handler.pl" +#ErrorDocument 402 http:/D:/zentao/www.example.com/subscription_info.html + +# Putting this all together, we can internationalize error responses. +# +# We use Alias to redirect any /error/HTTP_.html.var response to +# our collection of by-error message multi-language collections. We use +# includes to substitute the appropriate text. +# +# You can modify the messages' appearance without changing any of the +# default HTTP_.html.var files by adding the line: +# +# Alias /error/include/ "/your/include/path/" +# +# which allows you to create your own set of files by starting with the +# @exp_errordir@/include/ files and copying them to /your/include/path/, +# even on a per-VirtualHost basis. The default include files will display +# your Apache version number and your ServerAdmin email address regardless +# of the setting of ServerSignature. +# +# The internationalized error documents require mod_alias, mod_include +# and mod_negotiation. To activate them, uncomment the following 30 lines. + +# Alias /error/ "@exp_errordir@/" +# +# +# AllowOverride None +# Options IncludesNoExec +# AddOutputFilter Includes html +# AddHandler type-map var +# Order allow,deny +# Allow from all +# LanguagePriority en cs de es fr it nl sv pt-br ro +# ForceLanguagePriority Prefer Fallback +# +# +# ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var +# ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var +# ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var +# ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var +# ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var +# ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var +# ErrorDocument 410 /error/HTTP_GONE.html.var +# ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var +# ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var +# ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var +# ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var +# ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var +# ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var +# ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var +# ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var +# ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var +# ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var + +# The following directives modify normal HTTP response behavior to +# handle known problems with browser implementations. + +BrowserMatch "Mozilla/2" nokeepalive +BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 +BrowserMatch "RealPlayer 4\.0" force-response-1.0 +BrowserMatch "Java/1\.0" force-response-1.0 +BrowserMatch "JDK/1\.0" force-response-1.0 + +# The following directive disables redirects on non-GET requests for +# a directory that does not include the trailing slash. This fixes a +# problem with Microsoft WebFolders which does not appropriately handle +# redirects for folders with DAV methods. +# Same deal with Apple's DAV filesystem and Gnome VFS support for DAV. + +BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully +BrowserMatch "^WebDrive" redirect-carefully +BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully +BrowserMatch "^gnome-vfs" redirect-carefully + +# Allow server status reports generated by mod_status, +# with the URL of http://servername/server-status +# Change the ".your-domain.com" to match your domain to enable. + + + SetHandler server-status + Order deny,allow + Deny from all + Allow from 127.0.0.1 + + +# Allow remote server configuration reports, with the URL of +# http://servername/server-info (requires that mod_info.c be loaded). +# Change the ".your-domain.com" to match your domain to enable. + + + SetHandler server-info + Order deny,allow + Deny from all + Allow from 127.0.0.1 + + +### Section 3: Virtual Hosts +# +# VirtualHost: If you want to maintain multiple domains/hostnames on your +# machine you can setup VirtualHost containers for them. Most configurations +# use only name-based virtual hosts so the server doesn't need to worry about +# IP addresses. This is indicated by the asterisks in the directives below. +# +# Please see the documentation at +# +# for further details before you try to setup virtual hosts. +# +# You may use the command line option '-S' to verify your virtual host +# configuration. +# Use name-based virtual hosting. + +#NameVirtualHost * + +# VirtualHost example: +# Almost any Apache directive may go into a VirtualHost container. +# The first VirtualHost section is used for requests without a known +# server name. +# +# +# ServerAdmin webmaster@dummy-host.example.com +# DocumentRoot D:/zentao/www/docs/dummy-host.example.com +# ServerName dummy-host.example.com +# ErrorLog logs/dummy-host.example.com-error_log +# CustomLog logs/dummy-host.example.com-access_log common +# + + +NameVirtualHost * + + ServerName localhost:80 + DocumentRoot D:/zentao/www + + +### Section 4: Secure section + +# Secure (SSL/TLS) connections +# Include conf/ssl.conf +# +# Note: The following must must be present to support +# starting without SSL on platforms with no /dev/random equivalent +# but a statically compiled-in mod_ssl. +# +# +# SSLRandomSeed startup builtin +# SSLRandomSeed connect builtin +# + + + Include conf/ssl.conf + diff --git a/trunk/build/windows/xampp/apache_installservice.bat b/trunk/build/windows/xampp/apache_installservice.bat new file mode 100755 index 0000000000..dd00b1b5af --- /dev/null +++ b/trunk/build/windows/xampp/apache_installservice.bat @@ -0,0 +1,17 @@ +@echo off + +if "%OS%" == "Windows_NT" goto WinNT + +:Win9X +echo Don't be stupid! Win9x don't know Services +echo Please use apache_start.bat instead +goto exit + +:WinNT +echo Installing Apache2.2 as an Service +apache\bin\httpd -k install -n apachezt +echo Now we Start Apache +net start apachezt + +:exit +pause diff --git a/trunk/build/windows/xampp/apache_uninstallservice.bat b/trunk/build/windows/xampp/apache_uninstallservice.bat new file mode 100755 index 0000000000..78d45d32c3 --- /dev/null +++ b/trunk/build/windows/xampp/apache_uninstallservice.bat @@ -0,0 +1,18 @@ +@echo off + +if "%OS%" == "Windows_NT" goto WinNT + +:Win9X +echo Don't be stupid! Win9x don't know Services +echo Please use apache_stop.bat instead +goto exit + +:WinNT +echo Are you sure you wan't this? +echo now stopping Apache2.2 when it runs +net stop apachezt +echo Time to say good bye to apache2.2 :( +apache\bin\httpd -k uninstall -n apachezt + +:exit +pause diff --git a/trunk/build/windows/xampp/build.php b/trunk/build/windows/xampp/build.php new file mode 100644 index 0000000000..0984c21e43 --- /dev/null +++ b/trunk/build/windows/xampp/build.php @@ -0,0 +1,336 @@ +removeDir('./xampp/anonymous'); +$file->removeDir('./xampp/cgi-bin'); +$file->removeDir('./xampp/contrib'); +$file->removeDir('./xampp/contrib'); +$file->removeDir('./xampp/install'); +$file->removeDir('./xampp/nsi'); +$file->removeDir('./xampp/perl'); +$file->removeDir('./xampp/phpmyadmin'); +$file->removeDir('./xampp/sendmail'); +$file->removeDir('./xampp/security'); +$file->removeDir('./xampp/src'); +$file->batchRemoveFile('./xampp/tmp/*'); +$file->removeDir('./xampp/webdav'); +$file->removeFile('./xampp/setup_xampp.bat'); +$file->batchRemoveFile('./xampp/*.txt'); + +/* Process apache module. */ +$file->batchRemoveFile('./xampp/apache/*.txt'); +$file->batchRemoveFile('./xampp/apache/*.bat'); +$file->rename('./xampp/apache/modules', './xampp/apache/modulesold'); +$file->mkdir('./xampp/apache/modules'); + +$file->copyFile('./xampp/apache/modulesold/mod_auth_basic.so', './xampp/apache/modules/mod_auth_basic.so'); +$file->copyFile('./xampp/apache/modulesold/mod_mime_magic.so', './xampp/apache/modules/mod_mime_magic.so'); +$file->copyFile('./xampp/apache/modulesold/mod_mime.so', './xampp/apache/modules/mod_mime.so'); +$file->copyFile('./xampp/apache/modulesold/mod_expires.so', './xampp/apache/modules/mod_expires.so'); +$file->copyFile('./xampp/apache/modulesold/mod_env.so', './xampp/apache/modules/mod_env.so'); +$file->copyFile('./xampp/apache/modulesold/mod_rewrite.so', './xampp/apache/modules/mod_rewrite.so'); +$file->copyFile('./xampp/apache/modulesold/mod_vhost_alias.so','./xampp/apache/modules/mod_vhost_alias.so'); +$file->copyFile('./xampp/apache/modulesold/mod_setenvif.so', './xampp/apache/modules/mod_setenvif.so'); +$file->copyFile('./xampp/apache/modulesold/mod_autoindex.so', './xampp/apache/modules/mod_autoindex.so'); +$file->copyFile('./xampp/apache/modulesold/mod_authz_user.so', './xampp/apache/modules/mod_authz_user.so'); +$file->copyFile('./xampp/apache/modulesold/mod_authz_host.so', './xampp/apache/modules/mod_authz_host.so'); +$file->copyFile('./xampp/apache/modulesold/mod_alias.so', './xampp/apache/modules/mod_alias.so'); +$file->copyFile('./xampp/apache/modulesold/mod_dir.so', './xampp/apache/modules/mod_dir.so'); +$file->copyFile('./xampp/apache/modulesold/mod_deflate.so', './xampp/apache/modules/mod_deflate.so'); +$file->removeDir('./xampp/apache/modulesold'); + +/* Remove apache's error, icons, include, lib, logs directory. */ +$file->removeDir('./xampp/apache/error'); +$file->removeDir('./xampp/apache/icons'); +$file->removeDir('./xampp/apache/include'); +$file->removeDir('./xampp/apache/lib'); +$file->batchRemoveFile('./xampp/apache/logs/*.log'); + +$file->rename('./xampp/apache/bin', './xampp/apache/binold'); +$file->mkdir('./xampp/apache/bin'); + +$file->copyFile('./xampp/apache/binold/htpasswd.exe', './xampp/apache/bin/htpasswd.exe'); +$file->copyFile('./xampp/apache/binold/httpd.exe', './xampp/apache/bin/httpd.exe'); +$file->copyFile('./xampp/apache/binold/libapr-1.dll', './xampp/apache/bin/libapr-1.dll'); +$file->copyFile('./xampp/apache/binold/libapriconv-1.dll', './xampp/apache/bin/libapriconv-1.dll'); +$file->copyFile('./xampp/apache/binold/libaprutil-1.dll', './xampp/apache/bin/libaprutil-1.dll'); +$file->copyFile('./xampp/apache/binold/libhttpd.dll', './xampp/apache/bin/libhttpd.dll'); +$file->copyFile('./xampp/apache/binold/zlib1.dll', './xampp/apache/bin/zlib1.dll'); +$file->copyFile('./xampp/apache/binold/pv.exe', './xampp/apache/bin/pv.exe'); +$file->copyFile('./xampp/apache/binold/libeay32.dll', './xampp/apache/bin/libeay32.dll'); +$file->copyFile('./xampp/apache/binold/ssleay32.dll', './xampp/apache/bin/ssleay32.dll'); +$file->removeDir('./xampp/apache/binold'); + +/* Process the apache's config file. */ +$httpdConf = file_get_contents('./xampp/apache/conf/httpd.conf'); +$httpdConf = str_replace('LoadModule actions_module modules/mod_actions.so', '#LoadModule actions_module modules/mod_actions.so', $httpdConf); +$httpdConf = str_replace('LoadModule actions_module modules/mod_actions.so', '#LoadModule actions_module modules/mod_actions.so', $httpdConf); +$httpdConf = str_replace('LoadModule actions_module modules/mod_actions.so', '#LoadModule actions_module modules/mod_actions.so', $httpdConf); +$httpdConf = str_replace('LoadModule asis_module modules/mod_asis.so', '#LoadModule asis_module modules/mod_asis.so', $httpdConf); +$httpdConf = str_replace('LoadModule auth_digest_module modules/mod_auth_digest.so', '#LoadModule auth_digest_module modules/mod_auth_digest.so', $httpdConf); +$httpdConf = str_replace('LoadModule authn_default_module modules/mod_authn_default.so', '#LoadModule authn_default_module modules/mod_authn_default.so', $httpdConf); +$httpdConf = str_replace('LoadModule authn_file_module modules/mod_authn_file.so', '#LoadModule authn_file_module modules/mod_authn_file.so', $httpdConf); +$httpdConf = str_replace('LoadModule authz_default_module modules/mod_authz_default.so', '#LoadModule authz_default_module modules/mod_authz_default.so', $httpdConf); + +$httpdConf = file_get_contents('./xampp/apache/conf/httpd.conf'); +$httpdConf = str_replace('LoadModule actions_module modules/mod_actions.so', '#LoadModule actions_module modules/mod_actions.so', $httpdConf); +$httpdConf = str_replace('LoadModule actions_module modules/mod_actions.so', '#LoadModule actions_module modules/mod_actions.so', $httpdConf); +$httpdConf = str_replace('LoadModule actions_module modules/mod_actions.so', '#LoadModule actions_module modules/mod_actions.so', $httpdConf); +$httpdConf = str_replace('LoadModule asis_module modules/mod_asis.so', '#LoadModule asis_module modules/mod_asis.so', $httpdConf); +$httpdConf = str_replace('LoadModule auth_digest_module modules/mod_auth_digest.so', '#LoadModule auth_digest_module modules/mod_auth_digest.so', $httpdConf); +$httpdConf = str_replace('LoadModule authn_default_module modules/mod_authn_default.so', '#LoadModule authn_default_module modules/mod_authn_default.so', $httpdConf); +$httpdConf = str_replace('LoadModule authn_file_module modules/mod_authn_file.so', '#LoadModule authn_file_module modules/mod_authn_file.so', $httpdConf); +$httpdConf = str_replace('LoadModule authz_default_module modules/mod_authz_default.so', '#LoadModule authz_default_module modules/mod_authz_default.so', $httpdConf); +$httpdConf = str_replace('LoadModule authz_groupfile_module modules/mod_authz_groupfile.so','#LoadModule authz_groupfile_module modules/mod_authz_groupfile.so', $httpdConf); +$httpdConf = str_replace('LoadModule cgi_module modules/mod_cgi.so', '#LoadModule cgi_module modules/mod_cgi.so', $httpdConf); +$httpdConf = str_replace('LoadModule dav_lock_module modules/mod_dav_lock.so', '#LoadModule dav_lock_module modules/mod_dav_lock.so', $httpdConf); +$httpdConf = str_replace('LoadModule headers_module modules/mod_headers.so', '#LoadModule headers_module modules/mod_headers.so', $httpdConf); +$httpdConf = str_replace('LoadModule include_module modules/mod_include.so', '#LoadModule include_module modules/mod_include.so', $httpdConf); +$httpdConf = str_replace('LoadModule info_module modules/mod_info.so', '#LoadModule info_module modules/mod_info.so', $httpdConf); +$httpdConf = str_replace('LoadModule isapi_module modules/mod_isapi.so', '#LoadModule isapi_module modules/mod_isapi.so', $httpdConf); +$httpdConf = str_replace('LoadModule log_config_module modules/mod_log_config.so', '#LoadModule log_config_module modules/mod_log_config.so', $httpdConf); +$httpdConf = str_replace('LoadModule negotiation_module modules/mod_negotiation.so', '#LoadModule negotiation_module modules/mod_negotiation.so', $httpdConf); +$httpdConf = str_replace('LoadModule proxy_module modules/mod_proxy.so', '#LoadModule proxy_module modules/mod_proxy.so', $httpdConf); +$httpdConf = str_replace('LoadModule proxy_ajp_module modules/mod_proxy_ajp.so', '#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so', $httpdConf); +$httpdConf = str_replace('LoadModule ssl_module modules/mod_ssl.so', '#LoadModule ssl_module modules/mod_ssl.so', $httpdConf); +$httpdConf = str_replace('LoadModule status_module modules/mod_status.so', '#LoadModule status_module modules/mod_status.so', $httpdConf); +$httpdConf = str_replace('#LoadModule deflate_module modules/mod_deflate.so', 'LoadModule deflate_module modules/mod_deflate.so', $httpdConf); +$httpdConf = str_replace('#LoadModule expires_module modules/mod_expires.so', 'LoadModule expires_module modules/mod_expires.so', $httpdConf); + +$httpdConf = str_replace('Include "conf/extra/httpd-perl.conf"', '#Include "conf/extra/httpd-perl.conf"', $httpdConf); +$httpdConf = str_replace('Include "conf/extra/httpd-multilang-errordoc.conf"', '#Include "conf/extra/httpd-multilang-errordoc.conf"', $httpdConf); +$httpdConf = str_replace('Include "conf/extra/httpd-userdir.conf"', '#Include "conf/extra/httpd-userdir.conf"', $httpdConf); +$httpdConf = str_replace('Include "conf/extra/httpd-info.conf"', '#Include "conf/extra/httpd-info.conf"', $httpdConf); +$httpdConf = str_replace('Include "conf/extra/httpd-proxy.conf"', '#Include "conf/extra/httpd-proxy.conf"', $httpdConf); +$httpdConf = str_replace('Include "conf/extra/httpd-ssl.conf"', '#Include "conf/extra/httpd-ssl.conf"', $httpdConf); + +$httpdConf = explode("\n", $httpdConf); +foreach($httpdConf as $key => $line) +{ + $line = trim($line); + if(empty($line) or substr($line, 0, 1) == '#') unset($httpdConf[$key]); +} +$httpdConf = join("\n", $httpdConf); +file_put_contents('./xampp/apache/conf/httpd.conf', str_replace('88', '80', $httpdConf)); +file_put_contents('./xampp/apache/conf/httpd80.conf', str_replace('88', '80', $httpdConf)); +file_put_contents('./xampp/apache/conf/httpd88.conf', str_replace('80', '88', $httpdConf)); + +/* Move .htacces to .ztaccess. */ +$httpdDefaultConfig = './xampp/apache/conf/extra/httpd-default.conf'; +file_put_contents($httpdDefaultConfig, str_replace('.htaccess', '.ztaccess', file_get_contents($httpdDefaultConfig))); + +/* Remove useless config files. */ +$file->removeDir('./xampp/apache/conf/ssl.crl'); +$file->removeDir('./xampp/apache/conf/ssl.crt'); +$file->removeDir('./xampp/apache/conf/ssl.csr'); +$file->removeDir('./xampp/apache/conf/ssl.key'); +$file->removeFile('./xampp/apache/conf/extra/httpd-ajp.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-proxy.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-perl.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-dav.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-info.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-multilang-errordoc.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-ssl.conf'); +$file->removeFile('./xampp/apache/conf/extra/httpd-userdir.conf'); + +/* Empty the htdocs directory. */ +$file->removeDir('./xampp/htdocs'); +$file->mkdir('./xampp/htdocs'); + +/* Process mysql. */ +$file->removeDir('./xampp/mysql/backup'); +$file->removeDir('./xampp/mysql/include'); +$file->removeDir('./xampp/mysql/lib'); +$file->removeDir('./xampp/mysql/scripts'); +$file->removeDir('./xampp/mysql/sql-bench'); + +/* Process mysql's bin directory. */ +$file->rename('./xampp/mysql/bin', './xampp/mysql/binold'); +$file->mkdir('./xampp/mysql/bin'); + +$file->copyFile('./xampp/mysql/binold/mysql.exe', './xampp/mysql/bin/mysql.exe'); +$file->copyFile('./xampp/mysql/binold/mysqld.exe', './xampp/mysql/bin/mysqld.exe'); +$file->copyFile('./xampp/mysql/binold/mysqldump.exe', './xampp/mysql/bin/mysqldump.exe'); +$file->copyFile('./xampp/mysql/binold/myisamchk.exe', './xampp/mysql/bin/myisamchk.exe'); +$file->copyFile('./xampp/mysql/binold/my.ini', './xampp/mysql/bin/my.ini'); + +$file->removeDir('./xampp/mysql/binold'); + +/* Process mysql's share diectory. */ +$file->rename('./xampp/mysql/share', './xampp/mysql/shareold'); +$file->mkdir('./xampp/mysql/share'); +$file->mkdir('./xampp/mysql/share/english'); +$file->copyFile('./xampp/mysql/shareold/english/errmsg.sys', './xampp/mysql/share/english/errmsg.sys'); +$file->removeDir('../xampp/mysql/shareold'); + +/* Process mysql's data directory. */ +$file->removeDir('./xampp/mysql/data/phpmyadmin'); +$file->removeDir('./xampp/mysql/data/test'); +$file->removeDir('./xampp/mysql/data/webauth'); +$file->removeDir('./xampp/mysql/data/cdcol'); +$file->batchRemoveFile('./xampp/mysql/data/ib*'); +$file->batchRemoveFile('./xampp/mysql/data/mysql*'); + +/* Remove mysql's useless config files. */ +$file->batchRemoveFile('./xampp/mysql/*.ini'); +$file->removeFile('./xampp/mysql/README'); +$file->removeFile('./xampp/mysql/COPYING'); + +/* Process mysql's conf file. */ +$myConf = file_get_contents('./xampp/mysql/bin/my.ini'); +$myConf = str_replace('#bind-address="127.0.0.1"', 'bind-address="127.0.0.1"', $myConf); +$myConf = str_replace('#skip-innodb', "default-storage-engine=MyISAM\nskip-innodb\n", $myConf); + +$myConf = explode("\n", $myConf); +foreach($myConf as $key => $line) +{ + $line = trim($line); + if(empty($line) or substr($line, 0, 1) == '#') unset($myConf[$key]); + if(stripos($line, 'innodb') === 0) unset($myConf[$key]); +} +$myConf = join("\n", $myConf); +$myConf = str_replace(' ', '', $myConf); +file_put_contents('./xampp/mysql/bin/my.ini', str_replace('3308', '3306', $myConf)); +file_put_contents('./xampp/mysql/bin/my3306.ini', str_replace('3308', '3306', $myConf)); +file_put_contents('./xampp/mysql/bin/my3308.ini', str_replace('3306', '3308', $myConf)); + +/* Processing php. */ +$file->removeDir('./xampp/php/cfg'); +$file->removeDir('./xampp/php/data'); +$file->removeDir('./xampp/php/DB'); +$file->removeDir('./xampp/php/dev'); +$file->removeDir('./xampp/php/docs'); +$file->removeDir('./xampp/php/PEAR'); +$file->removeDir('./xampp/php/tests'); +$file->removeDir('./xampp/php/Text'); +$file->removeDir('./xampp/php/tmp'); +$file->removeDir('./xampp/php/www'); +$file->batchRemoveFile('./xampp/php/dbunit*'); +$file->batchRemoveFile('./xampp/php/*.bat'); +$file->batchRemoveFile('./xampp/php/*.txt'); +$file->batchRemoveFile('./xampp/php/php.ini-*'); +$file->batchRemoveFile('./xampp/php/*.reg'); +$file->batchRemoveFile('./xampp/php/pci*'); +$file->batchRemoveFile('./xampp/php/*.phar'); +$file->batchRemoveFile('./xampp/php/php-*.exe'); +$file->batchRemoveFile('./xampp/php/phpcov'); +$file->batchRemoveFile('./xampp/php/phptok'); +$file->batchRemoveFile('./xampp/php/phpunit'); +$file->batchRemoveFile('./xampp/php/*.php'); + +$file->rename('./xampp/php/php5apache2_2.dll', './xampp/php/php5apache2_2.bak'); +$file->rename('./xampp/php/php5ts.dll', './xampp/php/php5ts.bak'); +$file->rename('./xampp/php/ssleay32.dll', './xampp/php/ssleay32.dll.bak'); +$file->rename('./xampp/php/libeay32.dll', './xampp/php/libeay32.dll.bak'); +$file->rename('./xampp/php/libsasl.dll', './xampp/php/libsasl.dll.bak'); +$file->batchRemoveFile('./xampp/php/*.dll'); +$file->rename('./xampp/php/php5apache2_2.bak', './xampp/php/php5apache2_2.dll'); +$file->rename('./xampp/php/php5ts.bak', './xampp/php/php5ts.dll'); +$file->rename('./xampp/php/ssleay32.dll.bak', './xampp/php/ssleay32.dll'); +$file->rename('./xampp/php/libeay32.dll.bak', './xampp/php/libeay32.dll'); +$file->rename('./xampp/php/libsasl.dll.bak', './xampp/apache/bin/libsasl.dll'); + +/* Process php ini file. */ +$phpConfig = file_get_contents('./xampp/php/php.ini'); +$phpConfig = str_replace('extension=php_curl.dll',';extension=php_curl.dll', $phpConfig); +$phpConfig = str_replace('extension=php_exif.dll',';extension=php_exif.dll', $phpConfig); +$phpConfig = str_replace('extension=php_gettext.dll',';extension=php_gettext.dll', $phpConfig); +$phpConfig = str_replace('extension=php_pdo_odbc.dll',';extension=php_pdo_odbc.dll', $phpConfig); +$phpConfig = str_replace('extension=php_pdo_sqlite.dll',';extension=php_pdo_sqlite.dll', $phpConfig); +$phpConfig = str_replace('extension=php_soap.dll',';extension=php_soap.dll', $phpConfig); +$phpConfig = str_replace('extension=php_sqlite.dll',';extension=php_sqlite.dll', $phpConfig); +$phpConfig = str_replace('extension=php_sqlite3.dll','extension=php_ldap.dll', $phpConfig); // load ldap extension. +$phpConfig = str_replace('extension=php_xmlrpc.dll','extension=php_openssl.dll', $phpConfig); // load openssl extension. + +/* Remove empty and comment lines. */ +$phpConfig = explode("\n", $phpConfig); +foreach($phpConfig as $key => $line) +{ + $line = trim($line); + if(empty($line)) unset($phpConfig[$key]); + if(substr($line, 0, 1) == ';') unset($phpConfig[$key]); + if(stripos($line, 'odbc') !== false) unset($phpConfig[$key]); + if(stripos($line, 'interbase') !== false) unset($phpConfig[$key]); + if(stripos($line, 'ibase') !== false) unset($phpConfig[$key]); + if(stripos($line, 'oci8') !== false) unset($phpConfig[$key]); + if(stripos($line, 'postgresql') !== false) unset($phpConfig[$key]); + if(stripos($line, 'pgsql') !== false) unset($phpConfig[$key]); + if(stripos($line, 'sybase') !== false) unset($phpConfig[$key]); + if(stripos($line, 'sybct') !== false) unset($phpConfig[$key]); + if(stripos($line, 'mssql') !== false) unset($phpConfig[$key]); + if(stripos($line, 'soap') !== false) unset($phpConfig[$key]); + if(stripos($line, 'eaccelerator') !== false) unset($phpConfig[$key]); + if(stripos($line, 'xdebug') !== false) unset($phpConfig[$key]); +} +$phpConfig = join("\n", $phpConfig); +$phpConfig = 'zend_extension = "\xampp\php\ext\ioncube_loader_win_5.3.dll"' . "\n" . $phpConfig; + +file_put_contents('./xampp/php/php.ini', str_replace(' ', '', $phpConfig)); + +/* Process php's ext directory. */ +$file->rename('./xampp/php/ext', './xampp/php/extold'); +$file->mkdir('./xampp/php/ext'); +$file->copyFile('./xampp/php/extold/php_bz2.dll', './xampp/php/ext/php_bz2.dll'); +$file->copyFile('./xampp/php/extold/php_gd2.dll', './xampp/php/ext/php_gd2.dll'); +$file->copyFile('./xampp/php/extold/php_imap.dll', './xampp/php/ext/php_imap.dll'); +$file->copyFile('./xampp/php/extold/php_mbstring.dll', './xampp/php/ext/php_mbstring.dll'); +$file->copyFile('./xampp/php/extold/php_mysql.dll', './xampp/php/ext/php_mysql.dll'); +$file->copyFile('./xampp/php/extold/php_mysqli.dll', './xampp/php/ext/php_mysqli.dll'); +$file->copyFile('./xampp/php/extold/php_pdo_mysql.dll', './xampp/php/ext/php_pdo_mysql.dll'); +$file->copyFile('./xampp/php/extold/php_sockets.dll', './xampp/php/ext/php_sockets.dll'); +$file->copyFile('./xampp/php/extold/php_openssl.dll', './xampp/php/ext/php_openssl.dll'); +$file->copyFile('./xampp/php/extold/php_ldap.dll', './xampp/php/ext/php_ldap.dll'); +$file->removeDir('./xampp/php/extold'); + +/* Process sqlbuddy. */ +if(!is_dir('./xampp/admin/')) $file->mkdir('./xampp/admin/'); +if(!is_dir('./xampp/admin/sqlbuddy')) +{ + echo `$sevenz x -y $sqlbuddy`; + $file->rename('./sqlbuddy', './xampp/admin/sqlbuddy'); + $file->copyFile($buildDir . '/sqlbuddyconfig.php', './xampp/admin/sqlbuddy/config.php'); +} + +/* Process control panel. */ +if(file_exists('./xampp/xampp-control.exe')) $file->rename('./xampp/xampp-control.exe', './xampp/zentaoamp-control-en.exe'); +$file->copyFile($buildDir . '/zentaoamp.exe', './xampp/zentaoamp-control-cn.exe'); +$file->batchRemoveFile('./xampp/xampp_s*'); + +/* Copy index.php. */ +$file->copyFile($buildDir . '/index.php', './xampp/htdocs/index.php'); + +/* Copy zentao.conf. */ +$file->copyFile($buildDir . '/zentao.conf', './xampp/apache/conf/extra/httpd-xampp.conf'); + +/* Copy ioncube loader. */ +$file->copyFile($buildDir . '/ioncube_loader_win_5.3.dll', './xampp/php/ext/ioncube_loader_win_5.3.dll'); + +/* Copy serive bat file. */ +$file->copyFile($buildDir . '/apache_installservice.bat', './xampp/apache_installservice.bat'); +$file->copyFile($buildDir . '/apache_uninstallservice.bat', './xampp/apache_uninstallservice.bat'); +$file->copyFile($buildDir . '/mysql_installservice.bat', './xampp/mysql_installservice.bat'); +$file->copyFile($buildDir . '/mysql_uninstallservice.bat', './xampp/mysql_uninstallservice.bat'); + +/* Copy the src of control panel and readme files. */ +$file->mkdir('./xampp/source'); +$file->copyDir($buildDir . "/src/xampp-usb-lite/", './xampp/source/zentaoamp'); +$file->copyFile($buildDir . "/readme.txt", './xampp/readme.txt'); diff --git a/trunk/build/windows/xampp/index.php b/trunk/build/windows/xampp/index.php new file mode 100644 index 0000000000..ee6dc35152 --- /dev/null +++ b/trunk/build/windows/xampp/index.php @@ -0,0 +1,83 @@ +langs['cn'] = '中文简体'; +$config->langs['en'] = 'EN'; + +$lang->cn->title = '欢迎使用禅道集成运行环境!'; +$lang->cn->poweredBy = "由xampp精简而来"; + +$lang->cn->links['zentao']['link'] = '/zentao/'; +$lang->cn->links['zentao']['text'] = '访问禅道'; +$lang->cn->links['zentao']['target'] = '_self'; +$lang->cn->links['official']['link'] = 'http://www.zentao.net/'; +$lang->cn->links['official']['text'] = '禅道官网'; +$lang->cn->links['official']['target'] = '_blank'; +$lang->cn->links['sqlbudyy']['link'] = '/sqlbuddy/'; +$lang->cn->links['sqlbudyy']['text'] = '数据库管理'; +$lang->cn->links['sqlbudyy']['target'] = '_blank'; +$lang->cn->links['phpinfo']['link'] = '?mode=phpinfo'; +$lang->cn->links['phpinfo']['text'] = 'PHP信息'; +$lang->cn->links['phpinfo']['target'] = '_blank'; + +$lang->en->title = 'Welcome to use zentao!'; +$lang->en->poweredBy = "reduced from xampp"; + +$lang->en->links['zentao']['link'] = '/zentao/'; +$lang->en->links['zentao']['text'] = 'ZenTao'; +$lang->en->links['zentao']['target'] = '_self'; +$lang->en->links['official']['link'] = 'http://www.zentao.net/'; +$lang->en->links['official']['text'] = 'Community'; +$lang->en->links['official']['target'] = '_blank'; +$lang->en->links['sqlbudyy']['link'] = '/sqlbuddy/'; +$lang->en->links['sqlbudyy']['text'] = 'MySQL'; +$lang->en->links['sqlbudyy']['target'] = '_blank'; +$lang->en->links['phpinfo']['link'] = '?mode=phpinfo'; +$lang->en->links['phpinfo']['text'] = 'PHP'; +$lang->en->links['phpinfo']['target'] = '_blank'; + +if(is_file('./my.php')) include './my.php'; + +$acceptLang = stripos($_SERVER['HTTP_ACCEPT_LANGUAGE'], 'zh-CN') !== false ? 'cn' : 'en'; +$acceptLang = isset($_GET['lang']) ? $_GET['lang'] : $acceptLang; +$clientLang = $lang->$acceptLang; +?> + + + + + + <?php echo $clientLang->title;?> + + + + + + + + + + + + + + +
    title;?>
    + poweredBy . ' '; + foreach($config->langs as $langCode => $langName) echo "$langName"; + ?> +
    + + diff --git a/trunk/build/windows/xampp/ioncube_loader_win_5.3.dll b/trunk/build/windows/xampp/ioncube_loader_win_5.3.dll new file mode 100644 index 0000000000..bbc7aa5edc Binary files /dev/null and b/trunk/build/windows/xampp/ioncube_loader_win_5.3.dll differ diff --git a/trunk/build/windows/xampp/mysql_installservice.bat b/trunk/build/windows/xampp/mysql_installservice.bat new file mode 100755 index 0000000000..91bab86f35 --- /dev/null +++ b/trunk/build/windows/xampp/mysql_installservice.bat @@ -0,0 +1,17 @@ +@echo off + +if "%OS%" == "Windows_NT" goto WinNT + +:Win9X +echo Don't be stupid! Win9x don't know Services +echo Please use mysql_start.bat instead +goto exit + +:WinNT +echo Installing mysql as an Service +mysql\bin\mysqld.exe --install mysqlzt +echo Now we Start mysql :) +net start mysqlzt + +:exit +pause diff --git a/trunk/build/windows/xampp/mysql_uninstallservice.bat b/trunk/build/windows/xampp/mysql_uninstallservice.bat new file mode 100755 index 0000000000..b484426541 --- /dev/null +++ b/trunk/build/windows/xampp/mysql_uninstallservice.bat @@ -0,0 +1,17 @@ +@echo off + +if "%OS%" == "Windows_NT" goto WinNT + +:Win9X +echo Don't be stupid! Win9x don't know Services +echo Please use mysql_stop.bat instead +goto exit + +:WinNT +echo Are you sure you wan't this? +echo now stopping mysql when it runs +net stop mysqlzt +mysql\bin\mysqld.exe --remove mysqlzt + +:exit +pause diff --git a/trunk/build/windows/xampp/readme.txt b/trunk/build/windows/xampp/readme.txt new file mode 100644 index 0000000000..f17be01f81 --- /dev/null +++ b/trunk/build/windows/xampp/readme.txt @@ -0,0 +1,14 @@ +Ϊ˱֤ʹã֮ǰϸĶ˵ + +1. һװ벿ijһ̷ĸĿ¼棬c:\xamppd:\xamppǿԵġ +2. ҪĶxamppĿ¼л⡣ +3. zentaoamp-control-cn.exeǸһװĿ壬zentaoamp-control-en.exeӢĿ塣ֹͨͣapache, mysql +4. ޷apache˿ںǷͻ˿ڳͻԿ88˿ڡȷϲǶ˿ڳͻ޷뿼ǰװvcл + 32λϵͳأhttp://www.microsoft.com/downloads/details.aspx?FamilyID=9B2DA534-3E03-4391-8A4D-074B9F2BC1BF + 64λϵͳأhttp://www.microsoft.com/download/en/details.aspx?displaylang=en&id=15336 +5. ޷mysql˿ںǷͻͻԿ3308˿ڡ +6. ϵͳĬϵĹԱʺadmin123456 +7. ݿĬϵrootΪա +8. 뿪ԶУͨ壬mysqlapacheװΪ񣬷Ϊapachezt, mysqlzt + +ϸĽܣʣhttp://www.zentao.net/help-read-79597.html diff --git a/trunk/build/windows/xampp/sqlbuddyconfig.php b/trunk/build/windows/xampp/sqlbuddyconfig.php new file mode 100644 index 0000000000..b8da11f9ab --- /dev/null +++ b/trunk/build/windows/xampp/sqlbuddyconfig.php @@ -0,0 +1,14 @@ + nil then + begin + if (FData^.dwFileFlagsMask and VS_FF_DEBUG) <> 0 then + Include(Result, ffDebug); + if (FData^.dwFileFlagsMask and VS_FF_PRERELEASE) <> 0 then + Include(Result, ffPreRelease); + if (FData^.dwFileFlagsMask and VS_FF_PATCHED) <> 0 then + Include(Result, ffPatched); + if (FData^.dwFileFlagsMask and VS_FF_PRIVATEBUILD) <> 0 then + Include(Result, ffPrivateBuild); + if (FData^.dwFileFlagsMask and VS_FF_INFOINFERRED ) <> 0 then + Include(Result, ffInfoInferred ); + if (FData^.dwFileFlagsMask and VS_FF_SPECIALBUILD ) <> 0 then + Include(Result, ffSpecialBuild ); + end; +end; + +function TFixedFileVersionInfo.GetFlags: TFixedFileInfoFlags; +begin + Result := []; + if FData <> nil then + begin + if (FData^.dwFileFlags and VS_FF_DEBUG) <> 0 then + Include(Result, ffDebug); + if (FData^.dwFileFlags and VS_FF_PRERELEASE) <> 0 then + Include(Result, ffPreRelease); + if (FData^.dwFileFlags and VS_FF_PATCHED) <> 0 then + Include(Result, ffPatched); + if (FData^.dwFileFlags and VS_FF_PRIVATEBUILD) <> 0 then + Include(Result, ffPrivateBuild); + if (FData^.dwFileFlags and VS_FF_INFOINFERRED ) <> 0 then + Include(Result, ffInfoInferred ); + if (FData^.dwFileFlags and VS_FF_SPECIALBUILD ) <> 0 then + Include(Result, ffSpecialBuild ); + end; +end; + +function TFixedFileVersionInfo.GetFileOperatingSystem: TVersionOperatingSystemFlags; +{$IFNDEF DFS_WIN32} +var + FileOS: word; +{$ENDIF} +begin + Result := []; + if FData <> nil then + begin + case HiWord(FData^.dwFileOS) of + VOS_DOS shr 16: Include(Result, vosDOS); + VOS_OS216 shr 16: Include(Result, vosOS2_16); + VOS_OS232 shr 16: Include(Result, vosOS2_32); + VOS_NT shr 16: Include(Result, vosNT); + else + Include(Result, vosUnknown); + end; + +{$IFDEF DFS_WIN32} + case LoWord(FData^.dwFileOS) of + LoWord(VOS__WINDOWS16): Include(Result, vosWindows16); + LoWord(VOS__PM16): Include(Result, vosPresentationManager16); + LoWord(VOS__PM32): Include(Result, vosPresentationManager32); + LoWord(VOS__WINDOWS32): Include(Result, vosWindows32); + else + Include(Result, vosUnknown); + end; +{$ELSE} + FileOS := LoWord(FData^.dwFileOS); + if FileOS = LoWord(VOS__WINDOWS16) then Include(Result, vosWindows16) + else if FileOS = LoWord(VOS__PM16) then Include(Result, vosPresentationManager16) + else if FileOS = LoWord(VOS__PM32) then Include(Result, vosPresentationManager32) + else if FileOS = LoWord(VOS__WINDOWS32) then Include(Result, vosWindows32) + else Include(Result, vosUnknown); +{$ENDIF} + end; +end; + +function TFixedFileVersionInfo.GetFileType: TVersionFileType; +begin + Result := vftUnknown; + if FData <> nil then + begin + case FData^.dwFileType of + VFT_APP: Result := vftApplication; + VFT_DLL: Result := vftDLL; + VFT_DRV: Result := vftDriver; + VFT_FONT: Result := vftFont; + VFT_VXD: Result := vftVXD; + VFT_STATIC_LIB: Result := vftStaticLib; + end; + end; +end; + +function TFixedFileVersionInfo.GetFileSubType: DWORD; +begin + if FData = nil then + Result := 0 + else begin + Result := FData^.dwFileSubtype; + end; +end; + +function TFixedFileVersionInfo.GetCreationDate: TDateTime; +{$IFDEF DFS_WIN32} +var + SysTime: TSystemTime; + FileTime: TFileTime; +begin + if FData = nil then + Result := 0 + else begin + FileTime.dwLowDateTime := FData^.dwFileDateLS; + FileTime.dwHighDateTime := FData^.dwFileDateMS; + if FileTimeToSystemTime(FileTime, SysTime) then + begin + Result := SystemTimeToDateTime(SysTime); + end else + Result := 0; + end; +{$ELSE} +var + SR: TSearchRec; +begin + { Fake it until I can figure out how to convert dwFileDateMS and LS } + Result := 0; + if assigned(FParent) then + begin + if FindFirst(FParent.GetResourceFilename, faAnyFile, SR) = 0 then + begin + Result := FileDateToDateTime(SR.Time); + FindClose(SR); + end; + end; +(* +var + BigNum: comp; +begin + if FData = nil then + Result := 0 + else begin + + BigNum := (FData^.dwFileDateMS * MaxLongInt) + FData^.dwFileDateLS; + BigNum := BigNum / 10000000; + { LS and MS is the number of 100 nanosecond intervals since 1/1/1601 } + { 10,000,000s of a second } + Result := EncodeDate(1601, 1, 1); + Result := BigNum..... + end; +*) + +{$ENDIF} +end; + + + +constructor TVersionNumberInformation.Create(MSVer, LSVer: DWORD); +begin + inherited Create; + + FValid := false; + FMostSignificant := MSVer; + FLeastSignificant := LSVer; +end; + +function TVersionNumberInformation.GetVersionNumber(Index: integer): word; +begin + Result := 0; + if FValid then + case Index of + IDX_VER_MAJOR: Result := HiWord(FMostSignificant); + IDX_VER_MINOR: Result := LoWord(FMostSignificant); + IDX_VER_RELEASE: Result := HiWord(FLeastSignificant); + IDX_VER_BUILD: Result := LoWord(FLeastSignificant) + end +end; + +function TVersionNumberInformation.GetVersionNumberString: string; +begin + if FValid then + begin + if FVersionNumberString = '' then + Result := Format('%d.%d.%d.%d', [Major, Minor, Release, Build]) + else + Result := FVersionNumberString; + end + else + Result := '' +end; + + + +{$IFDEF DFS_VERSION_INFO_AS_CLASS} +constructor TdfsVersionInfoResource.Create; +begin + inherited Create; + + FVersionInfo := nil; + FVersionInfoSize := 0; + FFilename := ''; + FTranslationIDIndex := 0; + FForceEXE := FALSE; + FTranslationIDs := TStringList.Create; + FFileVersion := TVersionNumberInformation.Create(0, 0); + FProductVersion := TVersionNumberInformation.Create(0, 0); + FFixedInfo := TFixedFileVersionInfo.Create(Self); +end; +{$ELSE} +constructor TdfsVersionInfoResource.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + + FVersionInfo := nil; + FVersionInfoSize := 0; + FFilename := ''; + FTranslationIDIndex := 0; + FForceEXE := FALSE; + FTranslationIDs := TStringList.Create; + FFileVersion := TVersionNumberInformation.Create(0, 0); + FProductVersion := TVersionNumberInformation.Create(0, 0); + FFixedInfo := TFixedFileVersionInfo.Create(Self); + FShowResource := [Low(TPreDef)..High(TPreDef)] +end; +{$ENDIF} + +destructor TdfsVersionInfoResource.Destroy; +begin + FFileVersion.Free; + FProductVersion.Free; + FFixedInfo.Free; + FTranslationIDs.Free; + if FVersionInfo <> nil then + FreeMem(FVersionInfo, FVersionInfoSize); + + inherited Destroy; +end; + +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} +procedure TdfsVersionInfoResource.Loaded; +begin + inherited Loaded; + + ReadVersionInfoData; +(* +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} + PopulateControls; +{$ENDIF} +*) +end; +{$ENDIF} + +procedure TdfsVersionInfoResource.SetFilename(const Val: TVersionFilename); +begin + FFilename := Val; + ReadVersionInfoData; +end; + +procedure TdfsVersionInfoResource.ReadVersionInfoData; +const + TRANSLATION_INFO = '\VarFileInfo\Translation'; +type + TTranslationPair = packed record + Lang, + CharSet: word; + end; + PTranslationIDList = ^TTranslationIDList; + TTranslationIDList = array[0..MAXINT div SizeOf(TTranslationPair)-1] of TTranslationPair; +var + QueryLen: UINT; + IDsLen: UINT; + Dummy: DWORD; + IDs: PTranslationIDList; + IDCount: integer; + FixedInfoData: PVSFixedFileInfo; + TempFilename: array[0..255] of char; +begin + FTranslationIDs.Clear; + FFixedInfo.Data := nil; + if FVersionInfo <> nil then + FreeMem(FVersionInfo, FVersionInfoSize); + StrPCopy(TempFileName, GetResourceFilename); + + { Denis Kopprasch: added a try-Except because GetFileVersionInfoSize can fail + with an invalid pointer or something like that! } + try + FVersionInfoSize := GetFileVersionInfoSize(TempFileName, Dummy); + except + FVersionInfoSize := 0; + end; + + if FVersionInfoSize = 0 then + begin + FVersionInfo := nil; + FFileVersion.Valid := false; + FProductVersion.Valid := false; + end else begin + GetMem(FVersionInfo, FVersionInfoSize); + GetFileVersionInfo(TempFileName, Dummy, FVersionInfoSize, FVersionInfo); + + VerQueryValue(FVersionInfo, '\', pointer(FixedInfoData), QueryLen); + FFixedInfo.Data := FixedInfoData; + if VerQueryValue(FVersionInfo, TRANSLATION_INFO, Pointer(IDs), IDsLen) then + begin + { Denis Kopprasch: if IDCount = 0, the for .. to ...-Statement is executed + several times (maybe infinite until error) if range checking off } + IDCount := IDsLen div SizeOf(TTranslationPair); + if (IDCount > 0) then + begin + for Dummy := 0 to IDCount-1 do + begin + {!!! Potential problem. Some of MS's stuff does this, some does not. Need to + figure a way to make it work with both.} +(* if IDs^[Dummy].Lang = 0 then + IDs^[Dummy].Lang := DEFAULT_LANG_ID; { Some of Microsoft's stuff does this } + if IDs^[Dummy].CharSet = 0 then + IDs^[Dummy].CharSet := DEFAULT_CHAR_SET_ID;*) + FTranslationIDs.Add(Format('%.4x%.4x', [IDs^[Dummy].Lang, IDs^[Dummy].CharSet])); + end; + end + else + if (IDCount = 0) and (IDsLen > 0) then + begin + { There was translation info, but there was not a full set. What's + there is usually a char set, so we have to swap things around. } + FTranslationIDs.Add(Format('%.4x%.4x', [DEFAULT_LANG_ID, IDs^[Dummy].Lang])); + end; + end; + + if FTranslationIDIndex >= FTranslationIDs.Count then + FTranslationIDIndex := 0; + + FFileVersion.Valid := true; + FFileVersion.FMostSignificant := FFixedInfo.GetFileVersionMS; + FFileVersion.FLeastSignificant := FFixedInfo.GetFileVersionLS; + FFileVersion.FVersionNumberString := GetVersionInfoString(IDX_FILEVERSION); + FProductVersion.Valid := true; + FProductVersion.FMostSignificant := FFixedInfo.GetProductVersionMS; + FProductVersion.FLeastSignificant := FFixedInfo.GetProductVersionLS; + FProductVersion.FVersionNumberString := GetVersionInfoString( + IDX_PRODUCTVERSION); + end; +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} + PopulateControls; +{$ENDIF} +end; + +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} +procedure TdfsVersionInfoResource.PopulateControls; +begin + if [csDesigning, csLoading] * ComponentState <> [] then exit; + + if assigned(FFileVersionLabel) then + FFileVersionLabel.Caption := FileVersion.AsString; + if assigned(FCopyrightLabel) then + FCopyrightLabel.Caption := LegalCopyright; + if assigned(FProductLabel) then + FProductLabel.Caption := ProductName; + if assigned(FDescriptionLabel) then + FDescriptionLabel.Caption := FileDescription; +{$IFDEF DFS_WIN32} + if Assigned (FVersionListView) then + BuildListView; +{$ELSE} + if assigned(FVersionGrid) then + BuildGrid; +{$ENDIF} +end; +{$ENDIF} + +function TdfsVersionInfoResource.GetResourceFilename: string; +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} +var + TempFilename: array[0..255] of char; +{$ENDIF} +begin + {$IFNDEF DFS_VERSION_INFO_AS_CLASS} + if FFilename = '' then + begin + if IsLibrary and (not FForceEXE) then + begin + GetModuleFileName(HInstance, TempFileName, SizeOf(TempFileName)-1); + Result := StrPas(TempFileName); + end else + Result := Application.EXEName; + end else + {$ENDIF} + Result := FFilename; +end; + + +function TdfsVersionInfoResource.GetVersionInfoString(Index: integer): string; +begin + if (Index >= Low(PREDEF_RESOURCES)) and (Index <= High(PREDEF_RESOURCES)) then + Result := GetResourceStr(PREDEF_RESOURCES[Index]) + else + Result := '' +end; + + +function TdfsVersionInfoResource.GetResourceStr(Index: string): string; +var + ResStr: PChar; + StrLen: UINT; + SubBlock: array[0..255] of char; + LangCharSet: string; +begin + if FTranslationIDIndex < FTranslationIDs.Count then + LangCharSet := FTranslationIDs[FTranslationIDIndex] + else + LangCharSet := DEFAULT_LANG_CHAR_SET; + StrPCopy(SubBlock, '\StringFileInfo\' + LangCharSet + '\' + Index); + if (FVersionInfo <> nil) and + VerQueryValue(FVersionInfo, SubBlock, Pointer(ResStr), StrLen) + then + Result := StrPas(ResStr) + else + Result := ''; +end; + + +procedure TdfsVersionInfoResource.SetTranslationIDIndex(Val: integer); +begin + if (Val > 0) and (Val < FTranslationIDs.Count) then + FTranslationIDIndex := Val; +end; + + +function TdfsVersionInfoResource.GetTranslationIDs: TStrings; +begin + Result := FTranslationIDs; +end; + + +procedure TdfsVersionInfoResource.SetForceEXE(Val: boolean); +begin + if FForceEXE <> Val then + begin + FForceEXE := Val; + ReadVersionInfoData; + end; +end; + + +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} +procedure TdfsVersionInfoResource.SetFileVersionLabel(Value: TLabel); +begin + FFileVersionLabel := Value; + if assigned(FFileVersionLabel) then + begin +{$IFDEF DFS_WIN32} + FFileVersionLabel.FreeNotification(Self); +{$ENDIF} + FShowResource := FShowResource - [pdFileVersion]; + PopulateControls; + end; +end; + +procedure TdfsVersionInfoResource.SetCopyrightLabel(Value: TLabel); +begin + FCopyrightLabel := Value; + if assigned(FCopyrightLabel) then + begin +{$IFDEF DFS_WIN32} + FCopyrightLabel.FreeNotification(Self); +{$ENDIF} + FShowResource := FShowResource - [pdLegalCopyright]; + PopulateControls; + end; +end; + +procedure TdfsVersionInfoResource.SetProductLabel(Value: TLabel); +begin + FProductLabel := Value; + if assigned(FProductLabel) then + begin +{$IFDEF DFS_WIN32} + FProductLabel.FreeNotification(Self); +{$ENDIF} + FShowResource := FShowResource - [pdProductName]; + PopulateControls; + end; +end; + +procedure TdfsVersionInfoResource.SetDescriptionLabel(Value: TLabel); +begin + FDescriptionLabel := Value; + if assigned(FDescriptionLabel) then + begin +{$IFDEF DFS_WIN32} + FDescriptionLabel.FreeNotification(Self); +{$ENDIF} + FShowResource := FShowResource - [pdFileDescription]; + PopulateControls; + end; +end; + +procedure TdfsVersionInfoResource.SetShowResource(Value: TPreDefs); +begin + if Value <> FShowResource then + begin + FShowResource:= Value; + PopulateControls; + end +end; + +{$IFDEF DFS_WIN32} +procedure TdfsVersionInfoResource.SetVersionListView (Value: TListView); +begin + FVersionListView := Value; + if Assigned(FVersionListView) then + begin + FVersionListView.FreeNotification(Self); + PopulateControls; + end; +end; + +{$ELSE} + +procedure TdfsVersionInfoResource.SetVersionGrid(Value: TStringGrid); +begin + FVersionGrid := Value; + if Assigned(FVersionGrid) then + begin +{$IFDEF DFS_WIN32} + FVersionGrid.FreeNotification(Self); +{$ENDIF} + PopulateControls; + end; +end; +{$ENDIF} + + +procedure TdfsVersionInfoResource.Notification(AComponent: TComponent; + Operation: TOperation); +begin + inherited Notification(AComponent, Operation); + if Operation = opRemove then + begin + if (AComponent = FFileVersionLabel) then + FFileVersionLabel := nil + else if (AComponent = FCopyrightLabel) then + FCopyRightLabel := nil + else if (AComponent = FProductLabel) then + FProductLabel := nil + else if (AComponent = FDescriptionLabel) then + FDescriptionLabel := nil +{$IFDEF DFS_WIN32} + else if (AComponent = FVersionListView) then + FVersionListView := nil; +{$ELSE} + else if (AComponent = FVersionGrid) then + FVersionGrid := nil; +{$ENDIF} + end; +end; +{$ENDIF} + +function TdfsVersionInfoResource.BuildFlags : string; +const + FLAG_STRING: array[TFixedFileInfoFlag] of string = ( + SFlagDebug, SFlagInfoInferred, SFlagPatched, SFlagPreRelease, + SFlagPrivate, SFlagSpecial + ); +var + AFlag: TFixedFileInfoFlag; +begin + Result := ''; + for AFlag := Low(TFixedFileInfoFlag) to High(TFixedFileInfoFlag) do + if AFlag in FixedInfo.Flags then + Result := Result + FLAG_STRING[AFlag] + ', '; + + if Length(Result) > 0 then + Result := Copy(Result, 1, Length(Result)-2); +end; + +{$IFNDEF DFS_VERSION_INFO_AS_CLASS} +{$IFDEF DFS_WIN32} +procedure TdfsVersionInfoResource.BuildListView; + + procedure Add_Item (StrId: integer; const Str: string); + var + NewItem : TListItem; + begin + if (Str <> '') and (TPreDef(StrId) in FShowResource) then + begin + NewItem := VersionListView.Items.Add; + NewItem.Caption := PREDEF_CAPTIONS[StrId]; + NewItem.SubItems.Add (Str) + end + end; + + procedure Add_Column (const Str: string); + var + NewColumn : TListColumn; + begin + NewColumn := VersionListView.Columns.Add; + NewColumn.Caption := Str; + NewColumn.Width := -2; { nifty! } + end; + +begin + if Assigned (VersionListView) then + with VersionListView do + begin + Columns.Clear; + Items.Clear; + +{ only the minimum parameters in the listview are forced: } + ViewStyle := vsReport; + ReadOnly := true; + ColumnClick := false; + Add_Column (SHeaderResource); + Add_Column (SHeaderValue); + + Add_Item (IDX_PRODUCTNAME, ProductName); + Add_Item (IDX_PRODUCTVERSION, ProductVersion.AsString); + Add_Item (IDX_COMPANYNAME, CompanyName); + Add_Item (IDX_LEGALCOPYRIGHT, LegalCopyright); + Add_Item (IDX_LEGALTRADEMARKS, LegalTrademarks); + Add_Item (IDX_FILEDESCRIPTION, FileDescription); + Add_Item (IDX_FILEVERSION, FileVersion.AsString); + Add_Item (IDX_INTERNALNAME, InternalName); + Add_Item (IDX_ORIGINALFILENAME, OriginalFilename); + Add_Item (IDX_BUILDFLAGS, BuildFlags); + Add_Item (IDX_COMMENTS, Comments); + end +end; + +{$ELSE} + +procedure TdfsVersionInfoResource.BuildGrid; +const + FLAG_STRING: array[TFixedFileInfoFlag] of string = ( + SFlagDebug, SFlagInfoInferred, SFlagPatched, SFlagPreRelease, + SFlagPrivate, SFlagSpecial + ); + + procedure AddGridRow(var RowNum: integer; StrID: integer; Str: string); + var + i: integer; + begin + if (Str <> '') and (TPreDef(StrId) in FShowResource) then + begin + with VersionGrid do + begin + Cells[0,RowNum] := PREDEF_CAPTIONS[StrID]; + Cells[1,RowNum] := Str; + i := Canvas.TextWidth(Str); + if i > ColWidths[1] then + ColWidths[1] := i + 4; + inc(RowNum); + end; + end; + end; + +var + i, FRow: Integer; + +begin + With VersionGrid do + begin + { Set Defaults } + FixedCols := 0; + FixedRows := 0; + ColCount := 2; + RowCount := 10; + Canvas.Font.Assign(Font); + DefaultRowHeight := Canvas.TextHeight(PREDEF_CAPTIONS[IDX_ORIGINALFILENAME]) + 2; + ColWidths[0] := Canvas.TextWidth(PREDEF_CAPTIONS[IDX_LEGALTRADEMARKS]) + 4; + ColWidths[1] := ClientWidth - COlWidths[0] - 1; + { Clear } + for i:= 0 to (ColCount-1) do + Cols[i].Clear; + + FRow := 0; + AddGridRow(FRow, IDX_PRODUCTNAME, ProductName); + AddGridRow(FRow, IDX_PRODUCTVERSION, ProductVersion.AsString); + AddGridRow(FRow, IDX_COMPANYNAME, CompanyName); + AddGridRow(FRow, IDX_LEGALCOPYRIGHT, LegalCopyright); + AddGridRow(FRow, IDX_LEGALTRADEMARKS, LegalTrademarks); + AddGridRow(FRow, IDX_FILEDESCRIPTION, FileDescription); + AddGridRow(FRow, IDX_FILEVERSION, FileVersion.AsString); + AddGridRow(FRow, IDX_INTERNALNAME, InternalName); + AddGridRow(FRow, IDX_ORIGINALFILENAME, OriginalFilename); + AddGridRow(FRow, IDX_BUILDFLAGS, BuildFlags); + AddGridRow(FRow, IDX_COMMENTS, Comments); + RowCount := FRow; + end; +end; +{$ENDIF} + +function TdfsVersionInfoResource.GetVersion: string; +begin + Result := DFS_COMPONENT_VERSION; +end; + +procedure TdfsVersionInfoResource.SetVersion(const Val: string); +begin + { empty write method, just needed to get it to show up in Object Inspector } +end; +{$ENDIF} + + +end. + + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/150px-Flag_of_Germany.svg.jpg b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/150px-Flag_of_Germany.svg.jpg new file mode 100755 index 0000000000..1e2fddbe2b Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/150px-Flag_of_Germany.svg.jpg differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/150px-Flag_of_the_United_States.svg.jpg b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/150px-Flag_of_the_United_States.svg.jpg new file mode 100755 index 0000000000..a1fa357a42 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/150px-Flag_of_the_United_States.svg.jpg differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Checked.bmp b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Checked.bmp new file mode 100755 index 0000000000..5ec8cc369c Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Checked.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Checked2.bmp b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Checked2.bmp new file mode 100755 index 0000000000..5ec8cc369c Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Checked2.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Open16x16.bmp b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Open16x16.bmp new file mode 100755 index 0000000000..b7ce70353f Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/Open16x16.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/UnChecked.bmp b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/UnChecked.bmp new file mode 100755 index 0000000000..8876742db7 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/UnChecked.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/XAMPP.ICO b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/XAMPP.ICO new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/XAMPP.ICO differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/xampp.gif b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/xampp.gif new file mode 100755 index 0000000000..ca556f9a8a Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gfx/xampp.gif differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/ggexclude.cfg b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/ggexclude.cfg new file mode 100755 index 0000000000..f0f85394d5 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/ggexclude.cfg @@ -0,0 +1,14 @@ +[exclude-form-class-property] +TDBGrid.*.FieldName +*.Origin + +[exclude-form-class] +TIBSQL +TIBDataSet +TIBQuery +TIBStoredProc +TIntegerField +TLargeintField +TFloatField +TIBStringField +TDateTimeField diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gnugettext.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gnugettext.pas new file mode 100755 index 0000000000..bcc3054ddc --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/gnugettext.pas @@ -0,0 +1,2995 @@ +{*------------------------------------------------------------------------------ + GNU gettext translation system for Delphi, Kylix, C++ Builder and others. + All parts of the translation system are kept in this unit. + + @author Lars B. Dybdahl and others + @version $LastChangedRevision$ + @see http://dybdahl.dk/dxgettext/ +-------------------------------------------------------------------------------} +unit gnugettext; +(**************************************************************) +(* *) +(* (C) Copyright by Lars B. Dybdahl and others *) +(* E-mail: Lars@dybdahl.dk, phone +45 70201241 *) +(* *) +(* Contributors: Peter Thornqvist, Troy Wolbrink, *) +(* Frank Andreas de Groot, Igor Siticov, *) +(* Jacques Garcia Vazquez, Igor Gitman *) +(* *) +(* See http://dybdahl.dk/dxgettext/ for more information *) +(* *) +(**************************************************************) + +// Information about this file: +// $LastChangedDate$ +// $LastChangedRevision$ +// $HeadURL$ + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// The names of any contributor may not be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +interface + +// If the conditional define DXGETTEXTDEBUG is defined, debugging log is activated. +// Use DefaultInstance.DebugLogToFile() to write the log to a file. +{ $define DXGETTEXTDEBUG} + +{$ifdef VER140} + // Delphi 6 + {$DEFINE DELPHI2007OROLDER} +{$ifdef MSWINDOWS} + {$DEFINE DELPHI6OROLDER} +{$endif} +{$endif} +{$ifdef VER150} + // Delphi 7 + {$DEFINE DELPHI2007OROLDER} +{$endif} +{$ifdef VER160} + // Delphi 8 + {$DEFINE DELPHI2007OROLDER} +{$endif} +{$ifdef VER170} + // Delphi 2005 + {$DEFINE DELPHI2007OROLDER} +{$endif} +{$ifdef VER180} + // Delphi 2006 + {$DEFINE DELPHI2007OROLDER} +{$endif} +{$ifdef VER190} + // Delphi 2007 + {$DEFINE DELPHI2007OROLDER} +{$endif} +{$ifdef VER200} + // Delphi 2009 with Unicode +{$endif} + +uses +{$ifdef MSWINDOWS} + Windows, +{$else} + Libc, +{$ifdef FPC} + CWString, +{$endif} +{$endif} + Classes, StrUtils, SysUtils, TypInfo; + +(*****************************************************************************) +(* *) +(* MAIN API *) +(* *) +(*****************************************************************************) + +type + {$IFNDEF UNICODE} + UnicodeString=WideString; + RawUtf8String=AnsiString; + RawByteString=Ansistring; + {$ELSE} + RawUtf8String=RawByteString; + {$ENDIF} + DomainString=string; + LanguageString=string; + ComponentNameString=string; + FilenameString=string; + MsgIdString=UnicodeString; + TranslatedUnicodeString=UnicodeString; + +// Main GNU gettext functions. See documentation for instructions on how to use them. +function _(const szMsgId: MsgIdString): TranslatedUnicodeString; +function gettext(const szMsgId: MsgIdString): TranslatedUnicodeString; +function dgettext(const szDomain: DomainString; const szMsgId: MsgIdString): TranslatedUnicodeString; +function dngettext(const szDomain: DomainString; const singular,plural: MsgIdString; Number:longint): TranslatedUnicodeString; +function ngettext(const singular,plural: MsgIdString; Number:longint): TranslatedUnicodeString; +procedure textdomain(const szDomain: DomainString); +function getcurrenttextdomain: DomainString; +procedure bindtextdomain(const szDomain: DomainString; const szDirectory: FilenameString); + +// Set language to use +procedure UseLanguage(LanguageCode: LanguageString); +function GetCurrentLanguage:LanguageString; + +// Translates a component (form, frame etc.) to the currently selected language. +// Put TranslateComponent(self) in the OnCreate event of all your forms. +// See the manual for documentation on these functions +type + TTranslator=procedure (obj:TObject) of object; + +procedure TP_Ignore(AnObject:TObject; const name:ComponentNameString); +procedure TP_IgnoreClass (IgnClass:TClass); +procedure TP_IgnoreClassProperty (IgnClass:TClass;const propertyname:ComponentNameString); +procedure TP_GlobalIgnoreClass (IgnClass:TClass); +procedure TP_GlobalIgnoreClassProperty (IgnClass:TClass;const propertyname:ComponentNameString); +procedure TP_GlobalHandleClass (HClass:TClass;Handler:TTranslator); +procedure TranslateComponent(AnObject: TComponent; const TextDomain:DomainString=''); +procedure RetranslateComponent(AnObject: TComponent; const TextDomain:DomainString=''); + +// Add more domains that resourcestrings can be extracted from. If a translation +// is not found in the default domain, this domain will be searched, too. +// This is useful for adding mo files for certain runtime libraries and 3rd +// party component libraries +procedure AddDomainForResourceString (const domain:DomainString); +procedure RemoveDomainForResourceString (const domain:DomainString); + +// Unicode-enabled way to get resourcestrings, automatically translated +// Use like this: ws:=LoadResStringW(@NameOfResourceString); +function LoadResString(ResStringRec: PResStringRec): widestring; +function LoadResStringW(ResStringRec: PResStringRec): UnicodeString; + +// This returns an empty string if not translated or translator name is not specified. +function GetTranslatorNameAndEmail:TranslatedUnicodeString; + + +(*****************************************************************************) +(* *) +(* ADVANCED FUNCTIONALITY *) +(* *) +(*****************************************************************************) + +const + DefaultTextDomain = 'default'; + +var + ExecutableFilename:FilenameString; // This is set to paramstr(0) or the name of the DLL you are creating. + +const + PreferExternal=false; // Set to true, to prefer external *.mo over embedded translation + +const + // Subversion source code version control version information + VCSVersion='$LastChangedRevision$'; + +type + EGnuGettext=class(Exception); + EGGProgrammingError=class(EGnuGettext); + EGGComponentError=class(EGnuGettext); + EGGIOError=class(EGnuGettext); + EGGAnsi2WideConvError=class(EGnuGettext); + +// This function will turn resourcestring hooks on or off, eventually with BPL file support. +// Please do not activate BPL file support when the package is in design mode. +const AutoCreateHooks=true; +procedure HookIntoResourceStrings (enabled:boolean=true; SupportPackages:boolean=false); + + + + +(*****************************************************************************) +(* *) +(* CLASS based implementation. *) +(* Use TGnuGettextInstance to have more than one language *) +(* in your application at the same time *) +(* *) +(*****************************************************************************) + +{$ifdef MSWINDOWS} +{$ifndef DELPHI6OROLDER} +{$WARN UNSAFE_TYPE OFF} +{$WARN UNSAFE_CODE OFF} +{$WARN UNSAFE_CAST OFF} +{$endif} +{$endif} + +type + TOnDebugLine = Procedure (Sender: TObject; const Line: String; var Discard: Boolean) of Object; // Set Discard to false if output should still go to ordinary debug log + TGetPluralForm=function (Number:Longint):Integer; + TDebugLogger=procedure (line: ansistring) of object; + +{*------------------------------------------------------------------------------ + Handles .mo files, in separate files or inside the exe file. + Don't use this class. It's for internal use. +-------------------------------------------------------------------------------} + TMoFile= + class /// Threadsafe. Only constructor and destructor are writing to memory + private + doswap: boolean; + public + Users:Integer; /// Reference count. If it reaches zero, this object should be destroyed. + constructor Create (filename:FilenameString;Offset,Size:int64); + destructor Destroy; override; + function gettext(const msgid: RawUtf8String;var found:boolean): RawUtf8String; // uses mo file and utf-8 + property isSwappedArchitecture:boolean read doswap; + private + N, O, T: Cardinal; /// Values defined at http://www.linuxselfhelp.com/gnu/gettext/html_chapter/gettext_6.html + startindex,startstep:integer; + {$ifdef mswindows} + mo: THandle; + momapping: THandle; + {$endif} + momemoryHandle:PAnsiChar; + momemory: PAnsiChar; + function autoswap32(i: cardinal): cardinal; + function CardinalInMem(baseptr: PAnsiChar; Offset: Cardinal): Cardinal; + end; + +{*------------------------------------------------------------------------------ + Handles all issues regarding a specific domain. + Don't use this class. It's for internal use. +-------------------------------------------------------------------------------} + TDomain= + class + private + Enabled:boolean; + vDirectory: FilenameString; + procedure setDirectory(const dir: FilenameString); + public + DebugLogger:TDebugLogger; + Domain: DomainString; + property Directory: FilenameString read vDirectory write setDirectory; + constructor Create; + destructor Destroy; override; + // Set parameters + procedure SetLanguageCode (const langcode:LanguageString); + procedure SetFilename (const filename:FilenameString); // Bind this domain to a specific file + // Get information + procedure GetListOfLanguages(list:TStrings); + function GetTranslationProperty(Propertyname: ComponentNameString): TranslatedUnicodeString; + function gettext(const msgid: RawUtf8String): RawUtf8String; // uses mo file and utf-8 + private + mofile:TMoFile; + SpecificFilename:FilenameString; + curlang: LanguageString; + OpenHasFailedBefore: boolean; + procedure OpenMoFile; + procedure CloseMoFile; + end; + +{*------------------------------------------------------------------------------ + Helper class for invoking events. +-------------------------------------------------------------------------------} + TExecutable= + class + procedure Execute; virtual; abstract; + end; + +{*------------------------------------------------------------------------------ + The main translation engine. +-------------------------------------------------------------------------------} + TGnuGettextInstance= + class + private + fOnDebugLine:TOnDebugLine; + CreatorThread:Cardinal; /// Only this thread can use LoadResString + public + Enabled:Boolean; /// Set this to false to disable translations + DesignTimeCodePage:Integer; /// See MultiByteToWideChar() in Win32 API for documentation + constructor Create; + destructor Destroy; override; + procedure UseLanguage(LanguageCode: LanguageString); + procedure GetListOfLanguages (const domain:DomainString; list:TStrings); // Puts list of language codes, for which there are translations in the specified domain, into list + {$ifndef UNICODE} + function gettext(const szMsgId: ansistring): TranslatedUnicodeString; overload; virtual; + function ngettext(const singular,plural:ansistring;Number:longint):TranslatedUnicodeString; overload; virtual; + {$endif} + function gettext(const szMsgId: MsgIdString): TranslatedUnicodeString; overload; virtual; + function gettext_NoExtract(const szMsgId: MsgIdString): TranslatedUnicodeString; + function ngettext(const singular,plural:MsgIdString;Number:longint):TranslatedUnicodeString; overload; virtual; + function ngettext_NoExtract(const singular,plural:MsgIdString;Number:longint):TranslatedUnicodeString; + function GetCurrentLanguage:LanguageString; + function GetTranslationProperty (const Propertyname:ComponentNameString):TranslatedUnicodeString; + function GetTranslatorNameAndEmail:TranslatedUnicodeString; + + // Form translation tools, these are not threadsafe. All TP_ procs must be called just before TranslateProperites() + procedure TP_Ignore(AnObject:TObject; const name:ComponentNameString); + procedure TP_IgnoreClass (IgnClass:TClass); + procedure TP_IgnoreClassProperty (IgnClass:TClass;propertyname:ComponentNameString); + procedure TP_GlobalIgnoreClass (IgnClass:TClass); + procedure TP_GlobalIgnoreClassProperty (IgnClass:TClass;propertyname:ComponentNameString); + procedure TP_GlobalHandleClass (HClass:TClass;Handler:TTranslator); + procedure TranslateProperties(AnObject: TObject; textdomain:DomainString=''); + procedure TranslateComponent(AnObject: TComponent; const TextDomain:DomainString=''); + procedure RetranslateComponent(AnObject: TComponent; const TextDomain:DomainString=''); + + // Multi-domain functions + {$ifndef UNICODE} + function dgettext(const szDomain: DomainString; const szMsgId: ansistring): TranslatedUnicodeString; overload; virtual; + function dngettext(const szDomain: DomainString; const singular,plural:ansistring;Number:longint):TranslatedUnicodeString; overload; virtual; + {$endif} + function dgettext(const szDomain: DomainString; const szMsgId: MsgIdString): TranslatedUnicodeString; overload; virtual; + function dgettext_NoExtract(const szDomain: DomainString; const szMsgId: MsgIdString): TranslatedUnicodeString; + function dngettext(const szDomain: DomainString; const singular,plural:MsgIdString;Number:longint):TranslatedUnicodeString; overload; virtual; + function dngettext_NoExtract(const szDomain: DomainString; const singular,plural:MsgIdString;Number:longint):TranslatedUnicodeString; + procedure textdomain(const szDomain: DomainString); + function getcurrenttextdomain: DomainString; + procedure bindtextdomain(const szDomain: DomainString; const szDirectory: FilenameString); + procedure bindtextdomainToFile (const szDomain: DomainString; const filename: FilenameString); // Also works with files embedded in exe file + + // Windows API functions + function LoadResString(ResStringRec: PResStringRec): UnicodeString; + + // Output all log info to this file. This may only be called once. + procedure DebugLogToFile (const filename:FilenameString; append:boolean=false); + procedure DebugLogPause (PauseEnabled:boolean); + property OnDebugLine: TOnDebugLine read fOnDebugLine write fOnDebugLine; // If set, all debug output goes here + {$ifndef UNICODE} + // Conversion according to design-time character set + function ansi2wideDTCP (const s:AnsiString):MsgIdString; // Convert using Design Time Code Page + {$endif} + protected + procedure TranslateStrings (sl:TStrings;const TextDomain:DomainString); + + // Override these three, if you want to inherited from this class + // to create a new class that handles other domain and language dependent + // issues + procedure WhenNewLanguage (const LanguageID:LanguageString); virtual; // Override to know when language changes + procedure WhenNewDomain (const TextDomain:DomainString); virtual; // Override to know when text domain changes. Directory is purely informational + procedure WhenNewDomainDirectory (const TextDomain:DomainString;const Directory:FilenameString); virtual; // Override to know when any text domain's directory changes. It won't be called if a domain is fixed to a specific file. + private + curlang: LanguageString; + curGetPluralForm:TGetPluralForm; + curmsgdomain: DomainString; + savefileCS: TMultiReadExclusiveWriteSynchronizer; + savefile: TextFile; + savememory: TStringList; + DefaultDomainDirectory:FilenameString; + domainlist: TStringList; /// List of domain names. Objects are TDomain. + TP_IgnoreList:TStringList; /// Temporary list, reset each time TranslateProperties is called + TP_ClassHandling:TList; /// Items are TClassMode. If a is derived from b, a comes first + TP_GlobalClassHandling:TList; /// Items are TClassMode. If a is derived from b, a comes first + TP_Retranslator:TExecutable; /// Cast this to TTP_Retranslator + {$ifdef DXGETTEXTDEBUG} + DebugLogCS:TMultiReadExclusiveWriteSynchronizer; + DebugLog:TStream; + DebugLogOutputPaused:Boolean; + {$endif} + function TP_CreateRetranslator:TExecutable; // Must be freed by caller! + procedure FreeTP_ClassHandlingItems; + {$ifdef DXGETTEXTDEBUG} + procedure DebugWriteln(line: ansistring); + {$endif} + procedure TranslateProperty(AnObject: TObject; PropInfo: PPropInfo; + TodoList: TStrings; const TextDomain:DomainString); + function Getdomain(const domain:DomainString; const DefaultDomainDirectory:FilenameString; const CurLang: LanguageString): TDomain; // Translates a single property of an object + end; + +const + LOCALE_SISO639LANGNAME = $59; // Used by Lazarus software development tool + LOCALE_SISO3166CTRYNAME = $5A; // Used by Lazarus software development tool + +var + DefaultInstance:TGnuGettextInstance; /// Default instance of the main API for singlethreaded applications. + +implementation + +{$ifndef MSWINDOWS} +{$ifndef LINUX} + 'This version of gnugettext.pas is only meant to be compiled with Kylix 3,' + 'Delphi 6, Delphi 7 and later versions. If you use other versions, please' + 'get the gnugettext.pas version from the Delphi 5 directory.' +{$endif} +{$endif} + +(**************************************************************************) +// Some comments on the implementation: +// This unit should be independent of other units where possible. +// It should have a small footprint in any way. +(**************************************************************************) +// TMultiReadExclusiveWriteSynchronizer is used instead of TCriticalSection +// because it makes this unit independent of the SyncObjs unit +(**************************************************************************) + +{$B-,R+,I+,Q+} + +type + TTP_RetranslatorItem= + class + obj:TObject; + Propname:ComponentNameString; + OldValue:TranslatedUnicodeString; + end; + TTP_Retranslator= + class (TExecutable) + TextDomain:DomainString; + Instance:TGnuGettextInstance; + constructor Create; + destructor Destroy; override; + procedure Remember (obj:TObject; PropName:ComponentNameString; OldValue:TranslatedUnicodeString); + procedure Execute; override; + private + list:TList; + end; + TEmbeddedFileInfo= + class + offset,size:int64; + end; + TFileLocator= + class // This class finds files even when embedded inside executable + constructor Create; + destructor Destroy; override; + procedure Analyze; // List files embedded inside executable + function FileExists (filename:FilenameString):boolean; + function GetMoFile (filename:FilenameString;DebugLogger:TDebugLogger):TMoFile; + procedure ReleaseMoFile (mofile:TMoFile); + private + basedirectory:FilenameString; + filelist:TStringList; //Objects are TEmbeddedFileInfo. Filenames are relative to .exe file + MoFilesCS:TMultiReadExclusiveWriteSynchronizer; + MoFiles:TStringList; // Objects are filenames+offset, objects are TMoFile + function ReadInt64 (str:TStream):int64; + end; + TGnuGettextComponentMarker= + class (TComponent) + public + LastLanguage:LanguageString; + Retranslator:TExecutable; + destructor Destroy; override; + end; + TClassMode= + class + HClass:TClass; + SpecialHandler:TTranslator; + PropertiesToIgnore:TStringList; // This is ignored if Handler is set + constructor Create; + destructor Destroy; override; + end; + TRStrinfo = record + strlength, stroffset: cardinal; + end; + TStrInfoArr = array[0..10000000] of TRStrinfo; + PStrInfoArr = ^TStrInfoArr; + TCharArray5=array[0..4] of ansichar; + THook= // Replaces a runtime library procedure with a custom procedure + class + public + constructor Create (OldProcedure, NewProcedure: pointer; FollowJump:boolean=false); + destructor Destroy; override; // Restores unhooked state + procedure Reset (FollowJump:boolean=false); // Disables and picks up patch points again + procedure Disable; + procedure Enable; + private + oldproc,newproc:Pointer; + Patch:TCharArray5; + Original:TCharArray5; + PatchPosition:PAnsiChar; + procedure Shutdown; // Same as destroy, except that object is not destroyed + end; + +var + // System information + Win32PlatformIsUnicode:boolean=False; + + // Information about files embedded inside .exe file + FileLocator:TFileLocator; + + // Hooks into runtime library functions + ResourceStringDomainListCS:TMultiReadExclusiveWriteSynchronizer; + ResourceStringDomainList:TStringList; + HookLoadResString:THook; + HookLoadStr:THook; + HookFmtLoadStr:THook; + +function GGGetEnvironmentVariable(const Name:widestring):widestring; +var + Len: integer; + W : WideString; +begin + Result := ''; + SetLength(W,1); + Len := Windows.GetEnvironmentVariableW(PWideChar(Name), PWideChar(W), 1); + if Len > 0 then begin + SetLength(Result, Len - 1); + Windows.GetEnvironmentVariableW(PWideChar(Name), PWideChar(Result), Len); + end; +end; + +function StripCRRawMsgId (s:RawUtf8String):RawUtf8String; +var + i:integer; +begin + i:=1; + while i<=length(s) do begin + if s[i]=#13 then delete (s,i,1) else inc (i); + end; + Result:=s; +end; + +function EnsureLineBreakInTranslatedString (s:RawUtf8String):RawUtf8String; +{$ifdef MSWINDOWS} +var + i:integer; +{$endif} +begin + {$ifdef MSWINDOWS} + Assert (sLinebreak=ansistring(#13#10)); + i:=1; + while i<=length(s) do begin + if (s[i]=#10) and (MidStr(s,i-1,1)<>#13) then begin + insert (#13,s,i); + inc (i,2); + end else + inc (i); + end; + {$endif} + Result:=s; +end; + +function IsWriteProp(Info: PPropInfo): Boolean; +begin + Result := Assigned(Info) and (Info^.SetProc <> nil); +end; + +function ResourceStringGettext(MsgId: MsgIdString): TranslatedUnicodeString; +var + i:integer; +begin + if (MsgID='') or (ResourceStringDomainListCS=nil) then begin + // This only happens during very complicated program startups that fail, + // or when Msgid='' + Result:=MsgId; + exit; + end; + ResourceStringDomainListCS.BeginRead; + try + for i:=0 to ResourceStringDomainList.Count-1 do begin + Result:=dgettext(ResourceStringDomainList.Strings[i], MsgId); + if Result<>MsgId then + break; + end; + finally + ResourceStringDomainListCS.EndRead; + end; +end; + +function gettext(const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + Result:=DefaultInstance.gettext(szMsgId); +end; + +{*------------------------------------------------------------------------------ + This is the main translation procedure used in programs. It takes a parameter, + looks it up in the translation dictionary, and returns the translation. + If no translation is found, the parameter is returned. + + @param szMsgId The text, that should be displayed if no translation is found. +-------------------------------------------------------------------------------} +function _(const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + Result:=DefaultInstance.gettext(szMsgId); +end; + +{*------------------------------------------------------------------------------ + Translates a text, using a specified translation domain. + If no translation is found, the parameter is returned. + + @param szDomain Which translation domain that should be searched for a translation. + @param szMsgId The text, that should be displayed if no translation is found. +-------------------------------------------------------------------------------} +function dgettext(const szDomain: DomainString; const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + Result:=DefaultInstance.dgettext(szDomain, szMsgId); +end; + +function dngettext(const szDomain: DomainString; const singular,plural: MsgIdString; Number:longint): TranslatedUnicodeString; +begin + Result:=DefaultInstance.dngettext(szDomain,singular,plural,Number); +end; + +function ngettext(const singular,plural: MsgIdString; Number:longint): TranslatedUnicodeString; +begin + Result:=DefaultInstance.ngettext(singular,plural,Number); +end; + +procedure textdomain(const szDomain: Domainstring); +begin + DefaultInstance.textdomain(szDomain); +end; + +procedure SetGettextEnabled (enabled:boolean); +begin + DefaultInstance.Enabled:=enabled; +end; + +function getcurrenttextdomain: DomainString; +begin + Result:=DefaultInstance.getcurrenttextdomain; +end; + +procedure bindtextdomain(const szDomain: DomainString; const szDirectory: FilenameString); +begin + DefaultInstance.bindtextdomain(szDomain, szDirectory); +end; + +procedure TP_Ignore(AnObject:TObject; const name:FilenameString); +begin + DefaultInstance.TP_Ignore(AnObject, name); +end; + +procedure TP_GlobalIgnoreClass (IgnClass:TClass); +begin + DefaultInstance.TP_GlobalIgnoreClass(IgnClass); +end; + +procedure TP_IgnoreClass (IgnClass:TClass); +begin + DefaultInstance.TP_IgnoreClass(IgnClass); +end; + +procedure TP_IgnoreClassProperty (IgnClass:TClass;const propertyname:ComponentNameString); +begin + DefaultInstance.TP_IgnoreClassProperty(IgnClass,propertyname); +end; + +procedure TP_GlobalIgnoreClassProperty (IgnClass:TClass;const propertyname:ComponentNameString); +begin + DefaultInstance.TP_GlobalIgnoreClassProperty(IgnClass,propertyname); +end; + +procedure TP_GlobalHandleClass (HClass:TClass;Handler:TTranslator); +begin + DefaultInstance.TP_GlobalHandleClass (HClass, Handler); +end; + +procedure TranslateComponent(AnObject: TComponent; const TextDomain:DomainString=''); +begin + DefaultInstance.TranslateComponent(AnObject, TextDomain); +end; + +procedure RetranslateComponent(AnObject: TComponent; const TextDomain:DomainString=''); +begin + DefaultInstance.RetranslateComponent(AnObject, TextDomain); +end; + +{$ifdef MSWINDOWS} + +// These constants are only used in Windows 95 +// Thanks to Frank Andreas de Groot for this table +const + IDAfrikaans = $0436; IDAlbanian = $041C; + IDArabicAlgeria = $1401; IDArabicBahrain = $3C01; + IDArabicEgypt = $0C01; IDArabicIraq = $0801; + IDArabicJordan = $2C01; IDArabicKuwait = $3401; + IDArabicLebanon = $3001; IDArabicLibya = $1001; + IDArabicMorocco = $1801; IDArabicOman = $2001; + IDArabicQatar = $4001; IDArabic = $0401; + IDArabicSyria = $2801; IDArabicTunisia = $1C01; + IDArabicUAE = $3801; IDArabicYemen = $2401; + IDArmenian = $042B; IDAssamese = $044D; + IDAzeriCyrillic = $082C; IDAzeriLatin = $042C; + IDBasque = $042D; IDByelorussian = $0423; + IDBengali = $0445; IDBulgarian = $0402; + IDBurmese = $0455; IDCatalan = $0403; + IDChineseHongKong = $0C04; IDChineseMacao = $1404; + IDSimplifiedChinese = $0804; IDChineseSingapore = $1004; + IDTraditionalChinese = $0404; IDCroatian = $041A; + IDCzech = $0405; IDDanish = $0406; + IDBelgianDutch = $0813; IDDutch = $0413; + IDEnglishAUS = $0C09; IDEnglishBelize = $2809; + IDEnglishCanadian = $1009; IDEnglishCaribbean = $2409; + IDEnglishIreland = $1809; IDEnglishJamaica = $2009; + IDEnglishNewZealand = $1409; IDEnglishPhilippines = $3409; + IDEnglishSouthAfrica = $1C09; IDEnglishTrinidad = $2C09; + IDEnglishUK = $0809; IDEnglishUS = $0409; + IDEnglishZimbabwe = $3009; IDEstonian = $0425; + IDFaeroese = $0438; IDFarsi = $0429; + IDFinnish = $040B; IDBelgianFrench = $080C; + IDFrenchCameroon = $2C0C; IDFrenchCanadian = $0C0C; + IDFrenchCotedIvoire = $300C; IDFrench = $040C; + IDFrenchLuxembourg = $140C; IDFrenchMali = $340C; + IDFrenchMonaco = $180C; IDFrenchReunion = $200C; + IDFrenchSenegal = $280C; IDSwissFrench = $100C; + IDFrenchWestIndies = $1C0C; IDFrenchZaire = $240C; + IDFrisianNetherlands = $0462; IDGaelicIreland = $083C; + IDGaelicScotland = $043C; IDGalician = $0456; + IDGeorgian = $0437; IDGermanAustria = $0C07; + IDGerman = $0407; IDGermanLiechtenstein = $1407; + IDGermanLuxembourg = $1007; IDSwissGerman = $0807; + IDGreek = $0408; IDGujarati = $0447; + IDHebrew = $040D; IDHindi = $0439; + IDHungarian = $040E; IDIcelandic = $040F; + IDIndonesian = $0421; IDItalian = $0410; + IDSwissItalian = $0810; IDJapanese = $0411; + IDKannada = $044B; IDKashmiri = $0460; + IDKazakh = $043F; IDKhmer = $0453; + IDKirghiz = $0440; IDKonkani = $0457; + IDKorean = $0412; IDLao = $0454; + IDLatvian = $0426; IDLithuanian = $0427; + IDMacedonian = $042F; IDMalaysian = $043E; + IDMalayBruneiDarussalam = $083E; IDMalayalam = $044C; + IDMaltese = $043A; IDManipuri = $0458; + IDMarathi = $044E; IDMongolian = $0450; + IDNepali = $0461; IDNorwegianBokmol = $0414; + IDNorwegianNynorsk = $0814; IDOriya = $0448; + IDPolish = $0415; IDBrazilianPortuguese = $0416; + IDPortuguese = $0816; IDPunjabi = $0446; + IDRhaetoRomanic = $0417; IDRomanianMoldova = $0818; + IDRomanian = $0418; IDRussianMoldova = $0819; + IDRussian = $0419; IDSamiLappish = $043B; + IDSanskrit = $044F; IDSerbianCyrillic = $0C1A; + IDSerbianLatin = $081A; IDSesotho = $0430; + IDSindhi = $0459; IDSlovak = $041B; + IDSlovenian = $0424; IDSorbian = $042E; + IDSpanishArgentina = $2C0A; IDSpanishBolivia = $400A; + IDSpanishChile = $340A; IDSpanishColombia = $240A; + IDSpanishCostaRica = $140A; IDSpanishDominicanRepublic = $1C0A; + IDSpanishEcuador = $300A; IDSpanishElSalvador = $440A; + IDSpanishGuatemala = $100A; IDSpanishHonduras = $480A; + IDMexicanSpanish = $080A; IDSpanishNicaragua = $4C0A; + IDSpanishPanama = $180A; IDSpanishParaguay = $3C0A; + IDSpanishPeru = $280A; IDSpanishPuertoRico = $500A; + IDSpanishModernSort = $0C0A; IDSpanish = $040A; + IDSpanishUruguay = $380A; IDSpanishVenezuela = $200A; + IDSutu = $0430; IDSwahili = $0441; + IDSwedishFinland = $081D; IDSwedish = $041D; + IDTajik = $0428; IDTamil = $0449; + IDTatar = $0444; IDTelugu = $044A; + IDThai = $041E; IDTibetan = $0451; + IDTsonga = $0431; IDTswana = $0432; + IDTurkish = $041F; IDTurkmen = $0442; + IDUkrainian = $0422; IDUrdu = $0420; + IDUzbekCyrillic = $0843; IDUzbekLatin = $0443; + IDVenda = $0433; IDVietnamese = $042A; + IDWelsh = $0452; IDXhosa = $0434; + IDZulu = $0435; + +function GetWindowsLanguage: WideString; +var + langid: Cardinal; + langcode: WideString; + CountryName: array[0..4] of widechar; + LanguageName: array[0..4] of widechar; + works: boolean; +begin + // The return value of GetLocaleInfo is compared with 3 = 2 characters and a zero + works := 3 = GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, LanguageName, SizeOf(LanguageName)); + works := works and (3 = GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, CountryName, SizeOf(CountryName))); + if works then begin + // Windows 98, Me, NT4, 2000, XP and newer + LangCode := PWideChar(@(LanguageName[0])); + if lowercase(LangCode)='no' then LangCode:='nb'; + LangCode:=LangCode + '_' + PWideChar(@CountryName[0]); + end else begin + // This part should only happen on Windows 95. + langid := GetThreadLocale; + case langid of + IDBelgianDutch: langcode := 'nl_BE'; + IDBelgianFrench: langcode := 'fr_BE'; + IDBrazilianPortuguese: langcode := 'pt_BR'; + IDDanish: langcode := 'da_DK'; + IDDutch: langcode := 'nl_NL'; + IDEnglishUK: langcode := 'en_GB'; + IDEnglishUS: langcode := 'en_US'; + IDFinnish: langcode := 'fi_FI'; + IDFrench: langcode := 'fr_FR'; + IDFrenchCanadian: langcode := 'fr_CA'; + IDGerman: langcode := 'de_DE'; + IDGermanLuxembourg: langcode := 'de_LU'; + IDGreek: langcode := 'el_GR'; + IDIcelandic: langcode := 'is_IS'; + IDItalian: langcode := 'it_IT'; + IDKorean: langcode := 'ko_KO'; + IDNorwegianBokmol: langcode := 'nb_NO'; + IDNorwegianNynorsk: langcode := 'nn_NO'; + IDPolish: langcode := 'pl_PL'; + IDPortuguese: langcode := 'pt_PT'; + IDRussian: langcode := 'ru_RU'; + IDSpanish, IDSpanishModernSort: langcode := 'es_ES'; + IDSwedish: langcode := 'sv_SE'; + IDSwedishFinland: langcode := 'sv_FI'; + else + langcode := 'C'; + end; + end; + Result := langcode; +end; +{$endif} + +{$ifndef UNICODE} +function LoadResStringA(ResStringRec: PResStringRec): ansistring; +begin + Result:=DefaultInstance.LoadResString(ResStringRec); +end; +{$endif} + +function GetTranslatorNameAndEmail:TranslatedUnicodeString; +begin + Result:=DefaultInstance.GetTranslatorNameAndEmail; +end; + +procedure UseLanguage(LanguageCode: LanguageString); +begin + DefaultInstance.UseLanguage(LanguageCode); +end; + +type + PStrData = ^TStrData; + TStrData = record + Ident: Integer; + Str: String; + end; + +function SysUtilsEnumStringModules(Instance: Longint; Data: Pointer): Boolean; +{$IFDEF MSWINDOWS} +var + Buffer: array [0..1023] of Char; // WideChar in Delphi 2008, AnsiChar before that +begin + with PStrData(Data)^ do begin + SetString(Str, Buffer, + LoadString(Instance, Ident, @Buffer[0], sizeof(Buffer))); + Result := Str = ''; + end; +end; +{$ENDIF} +{$IFDEF LINUX} +var + rs:TResStringRec; + Module:HModule; +begin + Module:=Instance; + rs.Module:=@Module; + with PStrData(Data)^ do begin + rs.Identifier:=Ident; + Str:=System.LoadResString(@rs); + Result:=Str=''; + end; +end; +{$ENDIF} + +function SysUtilsFindStringResource(Ident: Integer): string; +var + StrData: TStrData; +begin + StrData.Ident := Ident; + StrData.Str := ''; + EnumResourceModules(SysUtilsEnumStringModules, @StrData); + Result := StrData.Str; +end; + +function SysUtilsLoadStr(Ident: Integer): string; +begin + {$ifdef DXGETTEXTDEBUG} + DefaultInstance.DebugWriteln ('Sysutils.LoadRes('+IntToStr(ident)+') called'); + {$endif} + Result := ResourceStringGettext(SysUtilsFindStringResource(Ident)); +end; + +function SysUtilsFmtLoadStr(Ident: Integer; const Args: array of const): string; +begin + {$ifdef DXGETTEXTDEBUG} + DefaultInstance.DebugWriteln ('Sysutils.FmtLoadRes('+IntToStr(ident)+',Args) called'); + {$endif} + FmtStr(Result, ResourceStringGettext(SysUtilsFindStringResource(Ident)),Args); +end; + +function LoadResString(ResStringRec: PResStringRec): widestring; +begin + Result:=DefaultInstance.LoadResString(ResStringRec); +end; + +function LoadResStringW(ResStringRec: PResStringRec): UnicodeString; +begin + Result:=DefaultInstance.LoadResString(ResStringRec); +end; + + + +function GetCurrentLanguage:LanguageString; +begin + Result:=DefaultInstance.GetCurrentLanguage; +end; + +{ TDomain } + +procedure TDomain.CloseMoFile; +begin + if mofile<>nil then begin + FileLocator.ReleaseMoFile(mofile); + mofile:=nil; + end; + OpenHasFailedBefore:=False; +end; + +destructor TDomain.Destroy; +begin + CloseMoFile; + inherited; +end; + +{$ifdef mswindows} +function GetLastWinError:widestring; +var + errcode:Cardinal; +begin + SetLength (Result,2000); + errcode:=GetLastError(); + Windows.FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM,nil,errcode,0,PWideChar(Result),2000,nil); + Result:=PWideChar(Result); +end; +{$endif} + +procedure TDomain.OpenMoFile; +var + filename: FilenameString; +begin + // Check if it is already open + if mofile<>nil then + exit; + + // Check if it has been attempted to open the file before + if OpenHasFailedBefore then + exit; + + if SpecificFilename<>'' then begin + filename:=SpecificFilename; + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Domain '+domain+' is bound to specific file '+filename); + {$endif} + end else begin + filename := Directory + curlang + PathDelim + 'LC_MESSAGES' + PathDelim + domain + '.mo'; + if (not FileLocator.FileExists(filename)) and (not fileexists(filename)) then begin + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Domain '+domain+': File does not exist, neither embedded or in file system: '+filename); + {$endif} + filename := Directory + MidStr(curlang, 1, 2) + PathDelim + 'LC_MESSAGES' + PathDelim + domain + '.mo'; + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Domain '+domain+' will attempt to use this file: '+filename); + {$endif} + end else begin + {$ifdef DXGETTEXTDEBUG} + if FileLocator.FileExists(filename) then + DebugLogger ('Domain '+domain+' will attempt to use this embedded file: '+filename) + else + DebugLogger ('Domain '+domain+' will attempt to use this file that was found on the file system: '+filename); + {$endif} + end; + end; + if (not FileLocator.FileExists(filename)) and (not fileexists(filename)) then begin + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Domain '+domain+' failed to locate the file: '+filename); + {$endif} + OpenHasFailedBefore:=True; + exit; + end; + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Domain '+domain+' now accesses the file.'); + {$endif} + mofile:=FileLocator.GetMoFile(filename, DebugLogger); + + {$ifdef DXGETTEXTDEBUG} + if mofile.isSwappedArchitecture then + DebugLogger ('.mo file is swapped (comes from another CPU architecture)'); + {$endif} + + // Check, that the contents of the file is utf-8 + if pos('CHARSET=UTF-8',uppercase(GetTranslationProperty('Content-Type')))=0 then begin + CloseMoFile; + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('The translation for the language code '+curlang+' (in '+filename+') does not have charset=utf-8 in its Content-Type. Translations are turned off.'); + {$endif} + {$ifdef MSWINDOWS} + MessageBoxW(0,PWideChar(widestring('The translation for the language code '+curlang+' (in '+filename+') does not have charset=utf-8 in its Content-Type. Translations are turned off.')),'Localization problem',MB_OK); + {$else} + writeln (stderr,'The translation for the language code '+curlang+' (in '+filename+') does not have charset=utf-8 in its Content-Type. Translations are turned off.'); + {$endif} + Enabled:=False; + end; +end; + +{$IFDEF UNICODE} +function utf8decode (s:RawByteString):UnicodeString; inline; +begin + Result:=UTF8ToWideString(s); +end; +{$endif} + +function TDomain.GetTranslationProperty( + Propertyname: ComponentNameString): TranslatedUnicodeString; +var + sl:TStringList; + i:integer; + s:string; +begin + Propertyname:=uppercase(Propertyname)+': '; + sl:=TStringList.Create; + try + sl.Text:=utf8decode(gettext('')); + for i:=0 to sl.Count-1 do begin + s:=sl.Strings[i]; + if uppercase(MidStr(s,1,length(Propertyname)))=Propertyname then begin + Result:=trim(MidStr(s,length(PropertyName)+1,maxint)); + + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('GetTranslationProperty('+PropertyName+') returns '''+Result+'''.'); + {$endif} + exit; + end; + end; + finally + FreeAndNil (sl); + end; + Result:=''; + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('GetTranslationProperty('+PropertyName+') did not find any value. An empty string is returned.'); + {$endif} +end; + +procedure TDomain.setDirectory(const dir: FilenameString); +begin + vDirectory := IncludeTrailingPathDelimiter(dir); + SpecificFilename:=''; + CloseMoFile; +end; + +procedure AddDomainForResourceString (const domain:DomainString); +begin + {$ifdef DXGETTEXTDEBUG} + DefaultInstance.DebugWriteln ('Extra domain for resourcestring: '+domain); + {$endif} + ResourceStringDomainListCS.BeginWrite; + try + if ResourceStringDomainList.IndexOf(domain)=-1 then + ResourceStringDomainList.Add (domain); + finally + ResourceStringDomainListCS.EndWrite; + end; +end; + +procedure RemoveDomainForResourceString (const domain:DomainString); +var + i:integer; +begin + {$ifdef DXGETTEXTDEBUG} + DefaultInstance.DebugWriteln ('Remove domain for resourcestring: '+domain); + {$endif} + ResourceStringDomainListCS.BeginWrite; + try + i:=ResourceStringDomainList.IndexOf(domain); + if i<>-1 then + ResourceStringDomainList.Delete (i); + finally + ResourceStringDomainListCS.EndWrite; + end; +end; + +procedure TDomain.SetLanguageCode(const langcode: LanguageString); +begin + CloseMoFile; + curlang:=langcode; +end; + +function GetPluralForm2EN(Number: Integer): Integer; +begin + Number:=abs(Number); + if Number=1 then Result:=0 else Result:=1; +end; + +function GetPluralForm1(Number: Integer): Integer; +begin + Result:=0; +end; + +function GetPluralForm2FR(Number: Integer): Integer; +begin + Number:=abs(Number); + if (Number=1) or (Number=0) then Result:=0 else Result:=1; +end; + +function GetPluralForm3LV(Number: Integer): Integer; +begin + Number:=abs(Number); + if (Number mod 10=1) and (Number mod 100<>11) then + Result:=0 + else + if Number<>0 then Result:=1 + else Result:=2; +end; + +function GetPluralForm3GA(Number: Integer): Integer; +begin + Number:=abs(Number); + if Number=1 then Result:=0 + else if Number=2 then Result:=1 + else Result:=2; +end; + +function GetPluralForm3LT(Number: Integer): Integer; +var + n1,n2:byte; +begin + Number:=abs(Number); + n1:=Number mod 10; + n2:=Number mod 100; + if (n1=1) and (n2<>11) then + Result:=0 + else + if (n1>=2) and ((n2<10) or (n2>=20)) then Result:=1 + else Result:=2; +end; + +function GetPluralForm3PL(Number: Integer): Integer; +var + n1,n2:byte; +begin + Number:=abs(Number); + n1:=Number mod 10; + n2:=Number mod 100; + + if Number=1 then Result:=0 + else if (n1>=2) and (n1<=4) and ((n2<10) or (n2>=20)) then Result:=1 + else Result:=2; +end; + +function GetPluralForm3RU(Number: Integer): Integer; +var + n1,n2:byte; +begin + Number:=abs(Number); + n1:=Number mod 10; + n2:=Number mod 100; + if (n1=1) and (n2<>11) then + Result:=0 + else + if (n1>=2) and (n1<=4) and ((n2<10) or (n2>=20)) then Result:=1 + else Result:=2; +end; + +function GetPluralForm3SK(Number: Integer): Integer; +begin + Number:=abs(Number); + if number=1 then Result:=0 + else if (number<5) and (number<>0) then Result:=1 + else Result:=2; +end; + +function GetPluralForm4SL(Number: Integer): Integer; +var + n2:byte; +begin + Number:=abs(Number); + n2:=Number mod 100; + if n2=1 then Result:=0 + else + if n2=2 then Result:=1 + else + if (n2=3) or (n2=4) then Result:=2 + else + Result:=3; +end; + +procedure TDomain.GetListOfLanguages(list: TStrings); +var + sr:TSearchRec; + more:boolean; + filename, path:FilenameString; + langcode:LanguageString; + i, j:integer; +begin + list.Clear; + + // Iterate through filesystem + more:=FindFirst (Directory+'*',faAnyFile,sr)=0; + try + while more do begin + if (sr.Attr and faDirectory<>0) and (sr.name<>'.') and (sr.name<>'..') then begin + filename := Directory + sr.Name + PathDelim + 'LC_MESSAGES' + PathDelim + domain + '.mo'; + if fileexists(filename) then begin + langcode:=lowercase(sr.name); + if list.IndexOf(langcode)=-1 then + list.Add(langcode); + end; + end; + more:=FindNext (sr)=0; + end; + finally + FindClose (sr); + end; + + // Iterate through embedded files + for i:=0 to FileLocator.filelist.Count-1 do begin + filename:=FileLocator.basedirectory+FileLocator.filelist.Strings[i]; + path:=Directory; + {$ifdef MSWINDOWS} + path:=uppercase(path); + filename:=uppercase(filename); + {$endif} + j:=length(path); + if MidStr(filename,1,j)=path then begin + path:=PathDelim + 'LC_MESSAGES' + PathDelim + domain + '.mo'; + {$ifdef MSWINDOWS} + path:=uppercase(path); + {$endif} + if MidStr(filename,length(filename)-length(path)+1,length(path))=path then begin + langcode:=lowercase(MidStr(filename,j+1,length(filename)-length(path)-j)); + langcode:=LeftStr(langcode,3)+uppercase(MidStr(langcode,4,maxint)); + if list.IndexOf(langcode)=-1 then + list.Add(langcode); + end; + end; + end; +end; + +procedure TDomain.SetFilename(const filename: FilenameString); +begin + CloseMoFile; + vDirectory := ''; + SpecificFilename:=filename; +end; + +function TDomain.gettext(const msgid: RawUtf8String): RawUtf8String; +var + found:boolean; +begin + if not Enabled then begin + Result:=msgid; + exit; + end; + if (mofile=nil) and (not OpenHasFailedBefore) then + OpenMoFile; + if mofile=nil then begin + {$ifdef DXGETTEXTDEBUG} + DebugLogger('.mo file is not open. Not translating "'+msgid+'"'); + {$endif} + Result := msgid; + end else begin + Result:=mofile.gettext(msgid,found); + {$ifdef DXGETTEXTDEBUG} + if found then + DebugLogger ('Found in .mo ('+Domain+'): "'+utf8encode(msgid)+'"->"'+utf8encode(Result)+'"') + else + DebugLogger ('Translation not found in .mo file ('+Domain+') : "'+utf8encode(msgid)+'"'); + {$endif} + end; +end; + +constructor TDomain.Create; +begin + inherited Create; + Enabled:=True; +end; + +{ TGnuGettextInstance } + +procedure TGnuGettextInstance.bindtextdomain(const szDomain:DomainString; + const szDirectory: FilenameString); +var + dir:FilenameString; +begin + dir:=IncludeTrailingPathDelimiter(szDirectory); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Text domain "'+szDomain+'" is now located at "'+dir+'"'); + {$endif} + getdomain(szDomain,DefaultDomainDirectory,CurLang).Directory := dir; + WhenNewDomainDirectory (szDomain, szDirectory); +end; + +constructor TGnuGettextInstance.Create; +begin + CreatorThread:=GetCurrentThreadId; + {$ifdef MSWindows} + DesignTimeCodePage:=CP_ACP; + {$endif} + {$ifdef DXGETTEXTDEBUG} + DebugLogCS:=TMultiReadExclusiveWriteSynchronizer.Create; + DebugLog:=TMemoryStream.Create; + DebugWriteln('Debug log started '+DateTimeToStr(Now)); + DebugWriteln('GNU gettext module version: '+VCSVersion); + DebugWriteln(''); + {$endif} + curGetPluralForm:=GetPluralForm2EN; + Enabled:=True; + curmsgdomain:=DefaultTextDomain; + savefileCS := TMultiReadExclusiveWriteSynchronizer.Create; + domainlist := TStringList.Create; + TP_IgnoreList:=TStringList.Create; + TP_IgnoreList.Sorted:=True; + TP_GlobalClassHandling:=TList.Create; + TP_ClassHandling:=TList.Create; + + // Set some settings + DefaultDomainDirectory := IncludeTrailingPathDelimiter(extractfilepath(ExecutableFilename))+'locale'; + + UseLanguage(''); + + bindtextdomain(DefaultTextDomain, DefaultDomainDirectory); + textdomain(DefaultTextDomain); + + // Add default properties to ignore + TP_GlobalIgnoreClassProperty(TComponent,'Name'); + TP_GlobalIgnoreClassProperty(TCollection,'PropName'); +end; + +destructor TGnuGettextInstance.Destroy; +begin + if savememory <> nil then begin + savefileCS.BeginWrite; + try + CloseFile(savefile); + finally + savefileCS.EndWrite; + end; + FreeAndNil(savememory); + end; + FreeAndNil (savefileCS); + FreeAndNil (TP_IgnoreList); + while TP_GlobalClassHandling.Count<>0 do begin + TObject(TP_GlobalClassHandling.Items[0]).Free; + TP_GlobalClassHandling.Delete(0); + end; + FreeAndNil (TP_GlobalClassHandling); + FreeTP_ClassHandlingItems; + FreeAndNil (TP_ClassHandling); + while domainlist.Count <> 0 do begin + domainlist.Objects[0].Free; + domainlist.Delete(0); + end; + FreeAndNil(domainlist); + {$ifdef DXGETTEXTDEBUG} + FreeAndNil (DebugLog); + FreeAndNil (DebugLogCS); + {$endif} + inherited; +end; + +{$ifndef UNICODE} +function TGnuGettextInstance.dgettext(const szDomain: DomainString; const szMsgId: ansistring): TranslatedUnicodeString; +begin + Result:=dgettext(szDomain, ansi2wideDTCP(szMsgId)); +end; +{$endif} + +function TGnuGettextInstance.dgettext(const szDomain: DomainString; + const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + if not Enabled then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Translation has been disabled. Text is not being translated: '+szMsgid); + {$endif} + Result:=szMsgId; + end else begin + Result:=UTF8Decode(EnsureLineBreakInTranslatedString(getdomain(szDomain,DefaultDomainDirectory,CurLang).gettext(StripCRRawMsgId(utf8encode(szMsgId))))); + + {$ifdef DXGETTEXTDEBUG} + if (szMsgId<>'') and (Result='') then + DebugWriteln (Format('Error: Translation of %s was an empty string. This may never occur.',[szMsgId])); + {$endif} + end; +end; + +function TGnuGettextInstance.dgettext_NoExtract(const szDomain: DomainString; + const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + // This one is very useful for translating text in variables. + // This can sometimes be necessary, and by using this function, + // the source code scanner will not trigger warnings. + Result:=dgettext(szDomain,szMsgId); +end; + +function TGnuGettextInstance.GetCurrentLanguage: LanguageString; +begin + Result:=curlang; +end; + +function TGnuGettextInstance.getcurrenttextdomain: DomainString; +begin + Result := curmsgdomain; +end; + +{$ifndef UNICODE} +function TGnuGettextInstance.gettext( + const szMsgId: ansistring): TranslatedUnicodeString; +begin + Result := dgettext(curmsgdomain, szMsgId); +end; +{$endif} + +function TGnuGettextInstance.gettext( + const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + Result := dgettext(curmsgdomain, szMsgId); +end; + +function TGnuGettextInstance.gettext_NoExtract( + const szMsgId: MsgIdString): TranslatedUnicodeString; +begin + // This one is very useful for translating text in variables. + // This can sometimes be necessary, and by using this function, + // the source code scanner will not trigger warnings. + Result:=gettext (szMsgId); +end; + +procedure TGnuGettextInstance.textdomain(const szDomain: DomainString); +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Changed text domain to "'+szDomain+'"'); + {$endif} + curmsgdomain := szDomain; + WhenNewDomain (szDomain); +end; + +function TGnuGettextInstance.TP_CreateRetranslator : TExecutable; +var + ttpr:TTP_Retranslator; +begin + ttpr:=TTP_Retranslator.Create; + ttpr.Instance:=self; + TP_Retranslator:=ttpr; + Result:=ttpr; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('A retranslator was created.'); + {$endif} +end; + +procedure TGnuGettextInstance.TP_GlobalHandleClass(HClass: TClass; + Handler: TTranslator); +var + cm:TClassMode; + i:integer; +begin + for i:=0 to TP_GlobalClassHandling.Count-1 do begin + cm:=TObject(TP_GlobalClassHandling.Items[i]) as TClassMode; + if cm.HClass=HClass then + raise EGGProgrammingError.Create ('You cannot set a handler for a class that has already been assigned otherwise.'); + if HClass.InheritsFrom(cm.HClass) then begin + // This is the place to insert this class + cm:=TClassMode.Create; + cm.HClass:=HClass; + cm.SpecialHandler:=Handler; + TP_GlobalClassHandling.Insert(i,cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('A handler was set for class '+HClass.ClassName+'.'); + {$endif} + exit; + end; + end; + cm:=TClassMode.Create; + cm.HClass:=HClass; + cm.SpecialHandler:=Handler; + TP_GlobalClassHandling.Add(cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('A handler was set for class '+HClass.ClassName+'.'); + {$endif} +end; + +procedure TGnuGettextInstance.TP_GlobalIgnoreClass(IgnClass: TClass); +var + cm:TClassMode; + i:integer; +begin + for i:=0 to TP_GlobalClassHandling.Count-1 do begin + cm:=TObject(TP_GlobalClassHandling.Items[i]) as TClassMode; + if cm.HClass=IgnClass then + raise EGGProgrammingError.Create ('You cannot add a class to the ignore list that is already on that list: '+IgnClass.ClassName+'. You should keep all TP_Global functions in one place in your source code.'); + if IgnClass.InheritsFrom(cm.HClass) then begin + // This is the place to insert this class + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + TP_GlobalClassHandling.Insert(i,cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Globally, class '+IgnClass.ClassName+' is being ignored.'); + {$endif} + exit; + end; + end; + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + TP_GlobalClassHandling.Add(cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Globally, class '+IgnClass.ClassName+' is being ignored.'); + {$endif} +end; + +procedure TGnuGettextInstance.TP_GlobalIgnoreClassProperty( + IgnClass: TClass; propertyname: ComponentNameString); +var + cm:TClassMode; + i,idx:integer; +begin + propertyname:=uppercase(propertyname); + for i:=0 to TP_GlobalClassHandling.Count-1 do begin + cm:=TObject(TP_GlobalClassHandling.Items[i]) as TClassMode; + if cm.HClass=IgnClass then begin + if Assigned(cm.SpecialHandler) then + raise EGGProgrammingError.Create ('You cannot ignore a class property for a class that has a handler set.'); + if not cm.PropertiesToIgnore.Find(propertyname,idx) then + cm.PropertiesToIgnore.Add(propertyname); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.'); + {$endif} + exit; + end; + if IgnClass.InheritsFrom(cm.HClass) then begin + // This is the place to insert this class + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + cm.PropertiesToIgnore.Add(propertyname); + TP_GlobalClassHandling.Insert(i,cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.'); + {$endif} + exit; + end; + end; + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + cm.PropertiesToIgnore.Add(propertyname); + TP_GlobalClassHandling.Add(cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.'); + {$endif} +end; + +procedure TGnuGettextInstance.TP_Ignore(AnObject: TObject; + const name: ComponentNameString); +begin + TP_IgnoreList.Add(uppercase(name)); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('On object with class name '+AnObject.ClassName+', ignore is set on '+name); + {$endif} +end; + +procedure TGnuGettextInstance.TranslateComponent(AnObject: TComponent; + const TextDomain: DomainString); +var + comp:TGnuGettextComponentMarker; +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('======================================================================'); + DebugWriteln ('TranslateComponent() was called for a component with name '+AnObject.Name+'.'); + {$endif} + comp:=AnObject.FindComponent('GNUgettextMarker') as TGnuGettextComponentMarker; + if comp=nil then begin + comp:=TGnuGettextComponentMarker.Create (nil); + comp.Name:='GNUgettextMarker'; + comp.Retranslator:=TP_CreateRetranslator; + TranslateProperties (AnObject, TextDomain); + AnObject.InsertComponent(comp); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('This is the first time, that this component has been translated. A retranslator component has been created for this component.'); + {$endif} + end else begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('This is not the first time, that this component has been translated.'); + {$endif} + if comp.LastLanguage<>curlang then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('ERROR: TranslateComponent() was called twice with different languages. This indicates an attempt to switch language at runtime, but by using TranslateComponent every time. This API has changed - please use RetranslateComponent() instead.'); + {$endif} + {$ifdef mswindows} + MessageBox (0,'This application tried to switch the language, but in an incorrect way. The programmer needs to replace a call to TranslateComponent with a call to RetranslateComponent(). The programmer should see the changelog of gnugettext.pas for more information.','Error',MB_OK); + {$else} + writeln (stderr,'This application tried to switch the language, but in an incorrect way. The programmer needs to replace a call to TranslateComponent with a call to RetranslateComponent(). The programmer should see the changelog of gnugettext.pas for more information.'); + {$endif} + end else begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('ERROR: TranslateComponent has been called twice, but with the same language chosen. This is a mistake, but in order to prevent that the application breaks, no exception is raised.'); + {$endif} + end; + end; + comp.LastLanguage:=curlang; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('======================================================================'); + {$endif} +end; + +procedure TGnuGettextInstance.TranslateProperty (AnObject:TObject; PropInfo:PPropInfo; TodoList:TStrings; const TextDomain:DomainString); +var + ppi:PPropInfo; + ws: TranslatedUnicodeString; + old: TranslatedUnicodeString; + compmarker:TComponent; + obj:TObject; + Propname:ComponentNameString; +begin + PropName:=string(PropInfo^.Name); + try + // Translate certain types of properties + case PropInfo^.PropType^.Kind of + {$IFDEF UNICODE} + // All dfm files returning tkUString + tkString, tkLString, tkWString, tkUString: + {$ELSE} + tkString, tkLString, tkWString: + {$ENDIF} + begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Translating '+AnObject.ClassName+'.'+PropName); + {$endif} + case PropInfo^.PropType^.Kind of + tkString, tkLString : + old := GetStrProp(AnObject, PropName); + tkWString : + old := GetWideStrProp(AnObject, PropName); + {$IFDEF UNICODE} + tkUString : + old := GetUnicodeStrProp(AnObject, PropName); + {$ENDIF} + else + raise Exception.Create ('Internal error: Illegal property type. This problem needs to be solved by a programmer, try to find a workaround.'); + end; + {$ifdef DXGETTEXTDEBUG} + if old='' then + DebugWriteln ('(Empty, not translated)') + else + DebugWriteln ('Old value: "'+old+'"'); + {$endif} + if (old <> '') and (IsWriteProp(PropInfo)) then begin + if TP_Retranslator<>nil then + (TP_Retranslator as TTP_Retranslator).Remember(AnObject, PropName, old); + ws := dgettext(textdomain,old); + if ws <> old then begin + ppi:=GetPropInfo(AnObject, Propname); + if ppi<>nil then begin + SetWideStrProp(AnObject, ppi, ws); + end else begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('ERROR: Property disappeared: '+Propname+' for object of type '+AnObject.ClassName); + {$endif} + end; + end; + end; + end { case item }; + tkClass: + begin + obj:=GetObjectProp(AnObject, PropName); + if obj<>nil then begin + if obj is TComponent then begin + compmarker := TComponent(obj).FindComponent('GNUgettextMarker'); + if Assigned(compmarker) then + exit; + end; + TodoList.AddObject ('',obj); + end; + end { case item }; + end { case }; + except + on E:Exception do + raise EGGComponentError.Create ('Property cannot be translated.'+sLineBreak+ + 'Add TP_GlobalIgnoreClassProperty('+AnObject.ClassName+','''+PropName+''') to your source code or use'+sLineBreak+ + 'TP_Ignore (self,''.'+PropName+''') to prevent this message.'+sLineBreak+ + 'Reason: '+e.Message); + end; +end; + +procedure TGnuGettextInstance.TranslateProperties(AnObject: TObject; textdomain:DomainString=''); +var + TodoList:TStringList; // List of Name/TObject's that is to be processed + DoneList:TStringList; // List of hex codes representing pointers to objects that have been done + i, j, Count: integer; + PropList: PPropList; + UPropName: ComponentNameString; + PropInfo: PPropInfo; + compmarker, + comp:TComponent; + cm, + currentcm:TClassMode; // currentcm is nil or contains special information about how to handle the current object + ObjectPropertyIgnoreList:TStringList; + objid:string; + Name:ComponentNameString; +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('----------------------------------------------------------------------'); + DebugWriteln ('TranslateProperties() was called for an object of class '+AnObject.ClassName+' with domain "'+textdomain+'".'); + {$endif} + if textdomain='' then + textdomain:=curmsgdomain; + if TP_Retranslator<>nil then + (TP_Retranslator as TTP_Retranslator).TextDomain:=textdomain; + {$ifdef FPC} + DoneList:=TCSStringList.Create; + TodoList:=TCSStringList.Create; + ObjectPropertyIgnoreList:=TCSStringList.Create; + {$else} + DoneList:=TStringList.Create; + TodoList:=TStringList.Create; + ObjectPropertyIgnoreList:=TStringList.Create; + {$endif} + try + TodoList.AddObject('', AnObject); + DoneList.Sorted:=True; + ObjectPropertyIgnoreList.Sorted:=True; + ObjectPropertyIgnoreList.Duplicates:=dupIgnore; + ObjectPropertyIgnoreList.CaseSensitive:=False; + DoneList.Duplicates:=dupError; + DoneList.CaseSensitive:=True; + + while TodoList.Count<>0 do begin + AnObject:=TodoList.Objects[0]; + Name:=TodoList.Strings[0]; + TodoList.Delete(0); + if (AnObject<>nil) and (AnObject is TPersistent) then begin + // Make sure each object is only translated once + Assert (sizeof(integer)=sizeof(TObject)); + objid:=IntToHex(integer(AnObject),8); + if DoneList.Find(objid,i) then begin + continue; + end else begin + DoneList.Add(objid); + end; + + ObjectPropertyIgnoreList.Clear; + + // Find out if there is special handling of this object + currentcm:=nil; + // First check the local handling instructions + for j:=0 to TP_ClassHandling.Count-1 do begin + cm:=TObject(TP_ClassHandling.Items[j]) as TClassMode; + if AnObject.InheritsFrom(cm.HClass) then begin + if cm.PropertiesToIgnore.Count<>0 then begin + ObjectPropertyIgnoreList.AddStrings(cm.PropertiesToIgnore); + end else begin + // Ignore the entire class + currentcm:=cm; + break; + end; + end; + end; + // Then check the global handling instructions + if currentcm=nil then + for j:=0 to TP_GlobalClassHandling.Count-1 do begin + cm:=TObject(TP_GlobalClassHandling.Items[j]) as TClassMode; + if AnObject.InheritsFrom(cm.HClass) then begin + if cm.PropertiesToIgnore.Count<>0 then begin + ObjectPropertyIgnoreList.AddStrings(cm.PropertiesToIgnore); + end else begin + // Ignore the entire class + currentcm:=cm; + break; + end; + end; + end; + if currentcm<>nil then begin + ObjectPropertyIgnoreList.Clear; + // Ignore or use special handler + if Assigned(currentcm.SpecialHandler) then begin + currentcm.SpecialHandler (AnObject); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Special handler activated for '+AnObject.ClassName); + {$endif} + end else begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Ignoring object '+AnObject.ClassName); + {$endif} + end; + continue; + end; + + Count := GetPropList(AnObject, PropList); + try + for j := 0 to Count - 1 do begin + PropInfo := PropList[j]; + {$IFDEF UNICODE} + if not (PropInfo^.PropType^.Kind in [tkString, tkLString, tkWString, tkClass, tkUString]) then + {$ELSE} + if not (PropInfo^.PropType^.Kind in [tkString, tkLString, tkWString, tkClass]) then + {$ENDIF} + continue; + UPropName:=uppercase(string(PropInfo^.Name)); + // Ignore properties that are meant to be ignored + if ((currentcm=nil) or (not currentcm.PropertiesToIgnore.Find(UPropName,i))) and + (not TP_IgnoreList.Find(Name+'.'+UPropName,i)) and + (not ObjectPropertyIgnoreList.Find(UPropName,i)) then begin + TranslateProperty (AnObject,PropInfo,TodoList,TextDomain); + end; // if + end; // for + finally + if Count<>0 then + FreeMem (PropList); + end; + if AnObject is TStrings then begin + if ((AnObject as TStrings).Text<>'') and (TP_Retranslator<>nil) then + (TP_Retranslator as TTP_Retranslator).Remember(AnObject, 'Text', (AnObject as TStrings).Text); + TranslateStrings (AnObject as TStrings,TextDomain); + end; + // Check for TCollection + if AnObject is TCollection then begin + for i := 0 to (AnObject as TCollection).Count - 1 do begin + // Only add the object if it's not totally ignored already + if not Assigned(currentcm) or not AnObject.InheritsFrom(currentcm.HClass) then + TodoList.AddObject('',(AnObject as TCollection).Items[i]); + end; + end; + if AnObject is TComponent then begin + for i := 0 to TComponent(AnObject).ComponentCount - 1 do begin + comp:=TComponent(AnObject).Components[i]; + if (not TP_IgnoreList.Find(uppercase(comp.Name),j)) then begin + // Only add the object if it's not totally ignored or translated already + if not Assigned(currentcm) or not AnObject.InheritsFrom(currentcm.HClass) then begin + compmarker := comp.FindComponent('GNUgettextMarker'); + if not Assigned(compmarker) then + TodoList.AddObject(uppercase(comp.Name),comp); + end; + end; + end; + end; + end { if AnObject<>nil }; + end { while todolist.count<>0 }; + finally + FreeAndNil (todolist); + FreeAndNil (ObjectPropertyIgnoreList); + FreeAndNil (DoneList); + end; + FreeTP_ClassHandlingItems; + TP_IgnoreList.Clear; + TP_Retranslator:=nil; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('----------------------------------------------------------------------'); + {$endif} +end; + +procedure TGnuGettextInstance.UseLanguage(LanguageCode: LanguageString); +var + i,p:integer; + dom:TDomain; + l2:string; +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln('UseLanguage('''+LanguageCode+'''); called'); + {$endif} + + if LanguageCode='' then begin + LanguageCode:=GGGetEnvironmentVariable('LANG'); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('LANG env variable is '''+LanguageCode+'''.'); + {$endif} + {$ifdef MSWINDOWS} + if LanguageCode='' then begin + LanguageCode:=GetWindowsLanguage; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Found Windows language code to be '''+LanguageCode+'''.'); + {$endif} + end; + {$endif} + p:=pos('.',LanguageCode); + if p<>0 then + LanguageCode:=LeftStr(LanguageCode,p-1); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Language code that will be set is '''+LanguageCode+'''.'); + {$endif} + end; + + curlang := LanguageCode; + for i:=0 to domainlist.Count-1 do begin + dom:=domainlist.Objects[i] as TDomain; + dom.SetLanguageCode (curlang); + end; + + l2:=lowercase(LeftStr(curlang,2)); + if (l2='en') or (l2='de') then curGetPluralForm:=GetPluralForm2EN else + if (l2='hu') or (l2='ko') or (l2='zh') or (l2='ja') or (l2='tr') then curGetPluralForm:=GetPluralForm1 else + if (l2='fr') or (l2='fa') or (lowercase(curlang)='pt_br') then curGetPluralForm:=GetPluralForm2FR else + if (l2='lv') then curGetPluralForm:=GetPluralForm3LV else + if (l2='ga') then curGetPluralForm:=GetPluralForm3GA else + if (l2='lt') then curGetPluralForm:=GetPluralForm3LT else + if (l2='ru') or (l2='uk') or (l2='hr') then curGetPluralForm:=GetPluralForm3RU else + if (l2='cs') or (l2='sk') then curGetPluralForm:=GetPluralForm3SK else + if (l2='pl') then curGetPluralForm:=GetPluralForm3PL else + if (l2='sl') then curGetPluralForm:=GetPluralForm4SL else begin + curGetPluralForm:=GetPluralForm2EN; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Plural form for the language was not found. English plurality system assumed.'); + {$endif} + end; + + WhenNewLanguage (curlang); + + {$ifdef DXGETTEXTDEBUG} + DebugWriteln(''); + {$endif} +end; + +procedure TGnuGettextInstance.TranslateStrings(sl: TStrings;const TextDomain:DomainString); +var + line: string; + i: integer; + s:TStringList; +begin + if sl.Count > 0 then begin + sl.BeginUpdate; + try + s:=TStringList.Create; + try + s.Assign (sl); + for i:=0 to s.Count-1 do begin + line:=s.Strings[i]; + if line<>'' then + s.Strings[i]:=dgettext(TextDomain,line); + end; + sl.Assign(s); + finally + FreeAndNil (s); + end; + finally + sl.EndUpdate; + end; + end; +end; + +function TGnuGettextInstance.GetTranslatorNameAndEmail: TranslatedUnicodeString; +begin + Result:=GetTranslationProperty('LAST-TRANSLATOR'); +end; + +function TGnuGettextInstance.GetTranslationProperty( + const Propertyname: ComponentNameString): TranslatedUnicodeString; +begin + Result:=getdomain(curmsgdomain,DefaultDomainDirectory,CurLang).GetTranslationProperty (Propertyname); +end; + +function TGnuGettextInstance.dngettext(const szDomain: DomainString; const singular, plural: MsgIdString; + Number: Integer): TranslatedUnicodeString; +var + org:MsgIdString; + trans:TranslatedUnicodeString; + idx:integer; + p:integer; +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('dngettext translation (domain '+szDomain+', number is '+IntTostr(Number)+') of '+singular+'/'+plural); + {$endif} + org:=singular+#0+plural; + trans:=dgettext(szDomain,org); + if org=trans then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Translation was equal to english version. English plural forms assumed.'); + {$endif} + idx:=GetPluralForm2EN(Number) + end else + idx:=curGetPluralForm(Number); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Index '+IntToStr(idx)+' will be used'); + {$endif} + while true do begin + p:=pos(#0,trans); + if p=0 then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Last translation used: '+utf8encode(trans)); + {$endif} + Result:=trans; + exit; + end; + if idx=0 then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Translation found: '+utf8encode(trans)); + {$endif} + Result:=LeftStr(trans,p-1); + exit; + end; + delete (trans,1,p); + dec (idx); + end; +end; + +function TGnuGettextInstance.dngettext_NoExtract(const szDomain: DomainString; + const singular, plural: MsgIdString; + Number: Integer): TranslatedUnicodeString; +begin + // This one is very useful for translating text in variables. + // This can sometimes be necessary, and by using this function, + // the source code scanner will not trigger warnings. + Result:=dngettext(szDomain,singular,plural,Number); +end; + +{$ifndef UNICODE} +function TGnuGettextInstance.ngettext(const singular, plural: ansistring; + Number: Integer): TranslatedUnicodeString; +begin + Result := dngettext(curmsgdomain, singular, plural, Number); +end; +{$endif} + +function TGnuGettextInstance.ngettext(const singular, plural: MsgIdString; + Number: Integer): TranslatedUnicodeString; +begin + Result := dngettext(curmsgdomain, singular, plural, Number); +end; + +function TGnuGettextInstance.ngettext_NoExtract(const singular, + plural: MsgIdString; Number: Integer): TranslatedUnicodeString; +begin + // This one is very useful for translating text in variables. + // This can sometimes be necessary, and by using this function, + // the source code scanner will not trigger warnings. + Result:=ngettext(singular,plural,Number); +end; + +procedure TGnuGettextInstance.WhenNewDomain(const TextDomain: DomainString); +begin + // This is meant to be empty. +end; + +procedure TGnuGettextInstance.WhenNewLanguage(const LanguageID: LanguageString); +begin + // This is meant to be empty. +end; + +procedure TGnuGettextInstance.WhenNewDomainDirectory(const TextDomain:DomainString; const Directory: FilenameString); +begin + // This is meant to be empty. +end; + +procedure TGnuGettextInstance.GetListOfLanguages(const domain: DomainString; + list: TStrings); +begin + getdomain(Domain,DefaultDomainDirectory,CurLang).GetListOfLanguages(list); +end; + +procedure TGnuGettextInstance.bindtextdomainToFile(const szDomain:DomainString; const filename: FilenameString); +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Text domain "'+szDomain+'" is now bound to file named "'+filename+'"'); + {$endif} + getdomain(szDomain,DefaultDomainDirectory,CurLang).SetFilename (filename); +end; + +procedure TGnuGettextInstance.DebugLogPause(PauseEnabled: boolean); +begin + {$ifdef DXGETTEXTDEBUG} + DebugLogOutputPaused:=PauseEnabled; + {$endif} +end; + +procedure TGnuGettextInstance.DebugLogToFile(const filename: FilenameString; append:boolean=false); +{$ifdef DXGETTEXTDEBUG} +var + fs:TFileStream; + marker:ansistring; +{$endif} +begin + {$ifdef DXGETTEXTDEBUG} + // Create the file if needed + if (not fileexists(filename)) or (not append) then + fileclose (filecreate (filename)); + + // Open file + fs:=TFileStream.Create (filename,fmOpenWrite or fmShareDenyWrite); + if append then + fs.Seek(0,soFromEnd); + + // Write header if appending + if fs.Position<>0 then begin + marker:=sLineBreak+'==========================================================================='+sLineBreak; + fs.WriteBuffer(marker[1],length(marker)); + end; + + // Copy the memorystream contents to the file + DebugLog.Seek(0,soFromBeginning); + fs.CopyFrom(DebugLog,0); + + // Make DebugLog point to the filestream + FreeAndNil (DebugLog); + DebugLog:=fs; + {$endif} +end; + +{$ifdef DXGETTEXTDEBUG} +procedure TGnuGettextInstance.DebugWriteln(line: ansistring); +Var + Discard: Boolean; +begin + Assert (DebugLogCS<>nil); + Assert (DebugLog<>nil); + + DebugLogCS.BeginWrite; + try + if DebugLogOutputPaused then + exit; + + if Assigned (fOnDebugLine) then begin + Discard := True; + fOnDebugLine (Self, Line, Discard); + If Discard then Exit; + end; + + line:=line+sLineBreak; + + // Ensure that memory usage doesn't get too big. + if (DebugLog is TMemoryStream) and (DebugLog.Position>1000000) then begin + line:=sLineBreak+sLineBreak+sLineBreak+sLineBreak+sLineBreak+ + 'Debug log halted because memory usage grew too much.'+sLineBreak+ + 'Specify a filename to store the debug log in or disable debug loggin in gnugettext.pas.'+ + sLineBreak+sLineBreak+sLineBreak+sLineBreak+sLineBreak; + DebugLogOutputPaused:=True; + end; + DebugLog.WriteBuffer(line[1],length(line)); + finally + DebugLogCS.EndWrite; + end; +end; +{$endif} + +function TGnuGettextInstance.Getdomain(const domain:DomainString; const DefaultDomainDirectory:FilenameString; const CurLang: LanguageString): TDomain; +// Retrieves the TDomain object for the specified domain. +// Creates one, if none there, yet. +var + idx: integer; +begin + idx := domainlist.IndexOf(Domain); + if idx = -1 then begin + Result := TDomain.Create; + {$ifdef DXGETTEXTDEBUG} + Result.DebugLogger:=DebugWriteln; + {$endif} + Result.Domain := Domain; + Result.Directory := DefaultDomainDirectory; + Result.SetLanguageCode(curlang); + domainlist.AddObject(Domain, Result); + end else begin + Result := domainlist.Objects[idx] as TDomain; + end; +end; + +function TGnuGettextInstance.LoadResString( + ResStringRec: PResStringRec): UnicodeString; +{$ifdef MSWINDOWS} +var + Len: Integer; + {$IFDEF UNICODE} + Buffer: array [0..1023] of widechar; + {$else} + Buffer: array [0..1023] of ansichar; + {$endif} +{$endif} +{$ifdef LINUX } +const + ResStringTableLen = 16; +type + ResStringTable = array [0..ResStringTableLen-1] of LongWord; +var + Handle: TResourceHandle; + Tab: ^ResStringTable; + ResMod: HMODULE; +{$endif } +begin + if ResStringRec=nil then + exit; + if ResStringRec.Identifier>=64*1024 then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('LoadResString was given an invalid ResStringRec.Identifier'); + {$endif} + Result:='ERROR'; + exit; + end else begin + {$ifdef LINUX} + // This works with Unicode if the Linux has utf-8 character set + // Result:=System.LoadResString(ResStringRec); + ResMod:=FindResourceHInstance(ResStringRec^.Module^); + Handle:=FindResource(ResMod, + PAnsiChar(ResStringRec^.Identifier div ResStringTableLen), PAnsiChar(6)); // RT_STRING + Tab:=Pointer(LoadResource(ResMod, Handle)); + if Tab=nil then + Result:='' + else + Result:=PWideChar(PAnsiChar(Tab)+Tab[ResStringRec^.Identifier mod ResStringTableLen]); + {$endif} + {$ifdef MSWINDOWS} + if not Win32PlatformIsUnicode then begin + SetString(Result, Buffer, + LoadString(FindResourceHInstance(ResStringRec.Module^), + ResStringRec.Identifier, Buffer, SizeOf(Buffer))) + end else begin + Result := ''; + Len := 0; + While Length(Result)<=Len+1 do begin + if Length(Result) = 0 then + SetLength(Result, 1024) + else + SetLength(Result, Length(Result) * 2); + Len := LoadStringW(FindResourceHInstance(ResStringRec.Module^), + ResStringRec.Identifier, PWideChar(Result), Length(Result)); + end; + SetLength(Result, Len); + end; + {$endif} + end; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Loaded resourcestring: '+utf8encode(Result)); + {$endif} + if CreatorThread<>GetCurrentThreadId then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('LoadResString was called from an invalid thread. Resourcestring was not translated.'); + {$endif} + end else + Result:=ResourceStringGettext(Result); +end; + +procedure TGnuGettextInstance.RetranslateComponent(AnObject: TComponent; + const TextDomain: DomainString); +var + comp:TGnuGettextComponentMarker; +begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('======================================================================'); + DebugWriteln ('RetranslateComponent() was called for a component with name '+AnObject.Name+'.'); + {$endif} + comp:=AnObject.FindComponent('GNUgettextMarker') as TGnuGettextComponentMarker; + if comp=nil then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Retranslate was called on an object that has not been translated before. An Exception is being raised.'); + {$endif} + raise EGGProgrammingError.Create ('Retranslate was called on an object that has not been translated before. Please use TranslateComponent() before RetranslateComponent().'); + end else begin + if comp.LastLanguage<>curlang then begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('The retranslator is being executed.'); + {$endif} + comp.Retranslator.Execute; + end else begin + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('The language has not changed. The retranslator is not executed.'); + {$endif} + end; + end; + comp.LastLanguage:=curlang; + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('======================================================================'); + {$endif} +end; + +procedure TGnuGettextInstance.TP_IgnoreClass(IgnClass: TClass); +var + cm:TClassMode; + i:integer; +begin + for i:=0 to TP_ClassHandling.Count-1 do begin + cm:=TObject(TP_ClassHandling.Items[i]) as TClassMode; + if cm.HClass=IgnClass then + raise EGGProgrammingError.Create ('You cannot add a class to the ignore list that is already on that list: '+IgnClass.ClassName+'.'); + if IgnClass.InheritsFrom(cm.HClass) then begin + // This is the place to insert this class + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + TP_ClassHandling.Insert(i,cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Locally, class '+IgnClass.ClassName+' is being ignored.'); + {$endif} + exit; + end; + end; + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + TP_ClassHandling.Add(cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Locally, class '+IgnClass.ClassName+' is being ignored.'); + {$endif} +end; + +procedure TGnuGettextInstance.TP_IgnoreClassProperty(IgnClass: TClass; + propertyname: ComponentNameString); +var + cm:TClassMode; + i:integer; +begin + propertyname:=uppercase(propertyname); + for i:=0 to TP_ClassHandling.Count-1 do begin + cm:=TObject(TP_ClassHandling.Items[i]) as TClassMode; + if cm.HClass=IgnClass then begin + if Assigned(cm.SpecialHandler) then + raise EGGProgrammingError.Create ('You cannot ignore a class property for a class that has a handler set.'); + cm.PropertiesToIgnore.Add(propertyname); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.'); + {$endif} + exit; + end; + if IgnClass.InheritsFrom(cm.HClass) then begin + // This is the place to insert this class + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + cm.PropertiesToIgnore.Add(propertyname); + TP_ClassHandling.Insert(i,cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Locally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.'); + {$endif} + exit; + end; + end; + cm:=TClassMode.Create; + cm.HClass:=IgnClass; + cm.PropertiesToIgnore.Add(propertyname); + TP_GlobalClassHandling.Add(cm); + {$ifdef DXGETTEXTDEBUG} + DebugWriteln ('Locally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.'); + {$endif} +end; + +procedure TGnuGettextInstance.FreeTP_ClassHandlingItems; +begin + while TP_ClassHandling.Count<>0 do begin + TObject(TP_ClassHandling.Items[0]).Free; + TP_ClassHandling.Delete(0); + end; +end; + +{$ifndef UNICODE} +function TGnuGettextInstance.ansi2wideDTCP(const s: ansistring): MsgIdString; +{$ifdef MSWindows} +var + len:integer; +{$endif} +begin +{$ifdef MSWindows} + if DesignTimeCodePage=CP_ACP then begin + // No design-time codepage specified. Using runtime codepage instead. +{$endif} + Result:=s; +{$ifdef MSWindows} + end else begin + len:=length(s); + if len=0 then + Result:='' + else begin + SetLength (Result,len); + len:=MultiByteToWideChar(DesignTimeCodePage,0,pansichar(s),len,pwidechar(Result),len); + if len=0 then + raise EGGAnsi2WideConvError.Create ('Cannot convert string to widestring:'+sLineBreak+s); + SetLength (Result,len); + end; + end; +{$endif} +end; +{$endif} + +{$ifndef UNICODE} +function TGnuGettextInstance.dngettext(const szDomain: DomainString; const singular, + plural: ansistring; Number: Integer): TranslatedUnicodeString; +begin + Result:=dngettext (szDomain, ansi2wideDTCP(singular), ansi2wideDTCP(plural), Number); +end; +{$endif} + +{ TClassMode } + +constructor TClassMode.Create; +begin + PropertiesToIgnore:=TStringList.Create; + PropertiesToIgnore.Sorted:=True; + PropertiesToIgnore.Duplicates:=dupError; + PropertiesToIgnore.CaseSensitive:=False; +end; + +destructor TClassMode.Destroy; +begin + FreeAndNil (PropertiesToIgnore); + inherited; +end; + +{ TFileLocator } + +procedure TFileLocator.Analyze; +var + s:RawByteString; + i:integer; + offset:int64; + fs:TFileStream; + fi:TEmbeddedFileInfo; + filename:FilenameString; + filename8bit:RawByteString; +const + arrch:array[0..43] of ansichar='6637DB2E-62E1-4A60-AC19-C23867046A89'#0#0#0#0#0#0#0#0; +begin + // Copy byte by byte, compatible with Delphi 2009 and older + SetLength (s,high(arrch)-low(arrch)+1); + for i:=0 to 43 do + s[i+1]:=arrch[i]; + + s:=MidStr(s,length(s)-7,8); + offset:=0; + for i:=8 downto 1 do + offset:=offset shl 8+ord(s[i]); + if offset=0 then + exit; + BaseDirectory:=ExtractFilePath(ExecutableFilename); + try + fs:=TFileStream.Create(ExecutableFilename,fmOpenRead or fmShareDenyNone); + try + while true do begin + fs.Seek(offset,soFromBeginning); + offset:=ReadInt64(fs); + if offset=0 then + exit; + fi:=TEmbeddedFileInfo.Create; + try + fi.Offset:=ReadInt64(fs); + fi.Size:=ReadInt64(fs); + SetLength (filename8bit, offset-fs.position); + fs.ReadBuffer (filename8bit[1],offset-fs.position); + filename:=trim(utf8decode(filename8bit)); + if PreferExternal and sysutils.fileexists(basedirectory+filename) then begin + // Disregard the internal version and use the external version instead + FreeAndNil (fi); + end else + filelist.AddObject(filename,fi); + except + FreeAndNil (fi); + raise; + end; + end; + finally + FreeAndNil (fs); + end; + except + {$ifdef DXGETTEXTDEBUG} + raise; + {$endif} + end; +end; + +constructor TFileLocator.Create; +begin + MoFilesCS:=TMultiReadExclusiveWriteSynchronizer.Create; + MoFiles:=TStringList.Create; + filelist:=TStringList.Create; + {$ifdef LINUX} + filelist.Duplicates:=dupError; + filelist.CaseSensitive:=True; + {$endif} + MoFiles.Sorted:=True; + MoFiles.Duplicates:=dupError; + MoFiles.CaseSensitive:=False; + {$ifdef MSWINDOWS} + filelist.Duplicates:=dupError; + filelist.CaseSensitive:=False; + {$endif} + filelist.Sorted:=True; +end; + +destructor TFileLocator.Destroy; +begin + while filelist.count<>0 do begin + filelist.Objects[0].Free; + filelist.Delete (0); + end; + FreeAndNil (filelist); + FreeAndNil (MoFiles); + FreeAndNil (MoFilesCS); + inherited; +end; + +function TFileLocator.FileExists(filename: FilenameString): boolean; +var + idx:integer; +begin + if LeftStr(filename,length(basedirectory))=basedirectory then begin + // Cut off basedirectory if the file is located beneath that base directory + filename:=MidStr(filename,length(basedirectory)+1,maxint); + end; + Result:=filelist.Find(filename,idx); +end; + +function TFileLocator.GetMoFile(filename: FilenameString; DebugLogger:TDebugLogger): TMoFile; +var + fi:TEmbeddedFileInfo; + idx:integer; + idxname:FilenameString; + Offset, Size: Int64; + realfilename:FilenameString; +begin + // Find real filename + offset:=0; + size:=0; + realfilename:=filename; + if LeftStr(filename,length(basedirectory))=basedirectory then begin + filename:=MidStr(filename,length(basedirectory)+1,maxint); + idx:=filelist.IndexOf(filename); + if idx<>-1 then begin + fi:=filelist.Objects[idx] as TEmbeddedFileInfo; + realfilename:=ExecutableFilename; + offset:=fi.offset; + size:=fi.size; + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Instead of '+filename+', using '+realfilename+' from offset '+IntTostr(offset)+', size '+IntToStr(size)); + {$endif} + end; + end; + + + {$ifdef DXGETTEXTDEBUG} + DebugLogger ('Reading .mo data from file '''+filename+''''); + {$endif} + + // Find TMoFile object + MoFilesCS.BeginWrite; + try + idxname:=realfilename+' //\\ '+IntToStr(offset); + if MoFiles.Find(idxname, idx) then begin + Result:=MoFiles.Objects[idx] as TMoFile; + end else begin + Result:=TMoFile.Create (realfilename, Offset, Size); + MoFiles.AddObject(idxname, Result); + end; + Inc (Result.Users); + finally + MoFilesCS.EndWrite; + end; +end; + +function TFileLocator.ReadInt64(str: TStream): int64; +begin + Assert (sizeof(Result)=8); + str.ReadBuffer(Result,8); +end; + +procedure TFileLocator.ReleaseMoFile(mofile: TMoFile); +var + i:integer; +begin + Assert (mofile<>nil); + + MoFilesCS.BeginWrite; + try + dec (mofile.Users); + if mofile.Users<=0 then begin + i:=MoFiles.Count-1; + while i>=0 do begin + if MoFiles.Objects[i]=mofile then begin + MoFiles.Delete(i); + FreeAndNil (mofile); + break; + end; + dec (i); + end; + end; + finally + MoFilesCS.EndWrite; + end; +end; + +{ TTP_Retranslator } + +constructor TTP_Retranslator.Create; +begin + list:=TList.Create; +end; + +destructor TTP_Retranslator.Destroy; +var + i:integer; +begin + for i:=0 to list.Count-1 do + TObject(list.Items[i]).Free; + FreeAndNil (list); + inherited; +end; + +procedure TTP_Retranslator.Execute; +var + i:integer; + sl:TStrings; + item:TTP_RetranslatorItem; + newvalue:TranslatedUnicodeString; + comp:TGnuGettextComponentMarker; + ppi:PPropInfo; +begin + for i:=0 to list.Count-1 do begin + item:=TObject(list.items[i]) as TTP_RetranslatorItem; + if item.obj is TComponent then begin + comp:=TComponent(item.obj).FindComponent('GNUgettextMarker') as TGnuGettextComponentMarker; + if Assigned(comp) and (self<>comp.Retranslator) then begin + comp.Retranslator.Execute; + Continue; + end; + end; + if item.obj is TStrings then begin + // Since we don't know the order of items in sl, and don't have + // the original .Objects[] anywhere, we cannot anticipate anything + // about the current sl.Strings[] and sl.Objects[] values. We therefore + // have to discard both values. We can, however, set the original .Strings[] + // value into the list and retranslate that. + sl:=TStringList.Create; + try + sl.Text:=item.OldValue; + Instance.TranslateStrings(sl,textdomain); + (item.obj as TStrings).BeginUpdate; + try + (item.obj as TStrings).Text:=sl.Text; + finally + (item.obj as TStrings).EndUpdate; + end; + finally + FreeAndNil (sl); + end; + end else begin + newValue:=instance.dgettext(textdomain,item.OldValue); + ppi:=GetPropInfo(item.obj, item.Propname); + if ppi<>nil then begin + SetWideStrProp(item.obj, ppi, newValue); + end else begin + {$ifdef DXGETTEXTDEBUG} + Instance.DebugWriteln ('ERROR: On retranslation, property disappeared: '+item.Propname+' for object of type '+item.obj.ClassName); + {$endif} + end; + end; + end; +end; + +procedure TTP_Retranslator.Remember(obj: TObject; PropName: ComponentNameString; + OldValue: TranslatedUnicodeString); +var + item:TTP_RetranslatorItem; +begin + item:=TTP_RetranslatorItem.Create; + item.obj:=obj; + item.Propname:=Propname; + item.OldValue:=OldValue; + list.Add(item); +end; + +{ TGnuGettextComponentMarker } + +destructor TGnuGettextComponentMarker.Destroy; +begin + FreeAndNil (Retranslator); + inherited; +end; + +{ THook } + +constructor THook.Create(OldProcedure, NewProcedure: pointer; FollowJump:boolean=false); +{ Idea and original code from Igor Siticov } +{ Modified by Jacques Garcia Vazquez and Lars Dybdahl } +begin + {$ifndef CPU386} + raise Exception.Create ('This procedure only works on Intel i386 compatible processors.'); + {$endif} + + oldproc:=OldProcedure; + newproc:=NewProcedure; + + Reset (FollowJump); +end; + +destructor THook.Destroy; +begin + Shutdown; + inherited; +end; + +procedure THook.Disable; +begin + Assert (PatchPosition<>nil,'Patch position in THook was nil when Disable was called'); + PatchPosition[0]:=Original[0]; + PatchPosition[1]:=Original[1]; + PatchPosition[2]:=Original[2]; + PatchPosition[3]:=Original[3]; + PatchPosition[4]:=Original[4]; +end; + +procedure THook.Enable; +begin + Assert (PatchPosition<>nil,'Patch position in THook was nil when Enable was called'); + PatchPosition[0]:=Patch[0]; + PatchPosition[1]:=Patch[1]; + PatchPosition[2]:=Patch[2]; + PatchPosition[3]:=Patch[3]; + PatchPosition[4]:=Patch[4]; +end; + +procedure THook.Reset(FollowJump: boolean); +var + offset:integer; + {$ifdef LINUX} + p:pointer; + pagesize:integer; + {$endif} + {$ifdef MSWindows} + ov: cardinal; + {$endif} +begin + if PatchPosition<>nil then + Shutdown; + + patchPosition := OldProc; + if FollowJump and (Word(OldProc^) = $25FF) then begin + // This finds the correct procedure if a virtual jump has been inserted + // at the procedure address + Inc(Integer(patchPosition), 2); // skip the jump + patchPosition := pansiChar(Pointer(pointer(patchPosition)^)^); + end; + offset:=integer(NewProc)-integer(pointer(patchPosition))-5; + + Patch[0] := ansichar($E9); + Patch[1] := ansichar(offset and 255); + Patch[2] := ansichar((offset shr 8) and 255); + Patch[3] := ansichar((offset shr 16) and 255); + Patch[4] := ansichar((offset shr 24) and 255); + + Original[0]:=PatchPosition[0]; + Original[1]:=PatchPosition[1]; + Original[2]:=PatchPosition[2]; + Original[3]:=PatchPosition[3]; + Original[4]:=PatchPosition[4]; + + {$ifdef MSWINDOWS} + if not VirtualProtect(Pointer(PatchPosition), 5, PAGE_EXECUTE_READWRITE, @ov) then + RaiseLastOSError; + {$endif} + {$ifdef LINUX} + pageSize:=sysconf (_SC_PAGE_SIZE); + p:=pointer(PatchPosition); + p:=pointer((integer(p) + PAGESIZE-1) and not (PAGESIZE-1) - pageSize); + if mprotect (p, pageSize, PROT_READ + PROT_WRITE + PROT_EXEC) <> 0 then + RaiseLastOSError; + {$endif} +end; + +procedure THook.Shutdown; +begin + Disable; + PatchPosition:=nil; +end; + +procedure HookIntoResourceStrings (enabled:boolean=true; SupportPackages:boolean=false); +begin + HookLoadResString.Reset (SupportPackages); + HookLoadStr.Reset (SupportPackages); + HookFmtLoadStr.Reset (SupportPackages); + if enabled then begin + HookLoadResString.Enable; + HookLoadStr.Enable; + HookFmtLoadStr.Enable; + end; +end; + +{ TMoFile } + +function TMoFile.autoswap32(i: cardinal): cardinal; +var + cnv1, cnv2: + record + case integer of + 0: (arr: array[0..3] of byte); + 1: (int: cardinal); + end; +begin + if doswap then begin + cnv1.int := i; + cnv2.arr[0] := cnv1.arr[3]; + cnv2.arr[1] := cnv1.arr[2]; + cnv2.arr[2] := cnv1.arr[1]; + cnv2.arr[3] := cnv1.arr[0]; + Result := cnv2.int; + end else + Result := i; +end; + +function TMoFile.CardinalInMem(baseptr: PansiChar; Offset: Cardinal): Cardinal; +var pc:^Cardinal; +begin + inc (baseptr,offset); + pc:=Pointer(baseptr); + Result:=pc^; + if doswap then + autoswap32(Result); +end; + +constructor TMoFile.Create(filename: FilenameString; Offset,Size:int64); +var + i:cardinal; + nn:integer; + {$ifdef linux} + mofile:TFileStream; + {$endif} +begin + if sizeof(i) <> 4 then + raise EGGProgrammingError.Create('TDomain in gnugettext is written for an architecture that has 32 bit integers.'); + + {$ifdef mswindows} + // Map the mo file into memory and let the operating system decide how to cache + mo:=createfile (PChar(filename),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0); + if mo=INVALID_HANDLE_VALUE then + raise EGGIOError.Create ('Cannot open file '+filename); + momapping:=CreateFileMapping (mo, nil, PAGE_READONLY, 0, 0, nil); + if momapping=0 then + raise EGGIOError.Create ('Cannot create memory map on file '+filename); + momemoryHandle:=MapViewOfFile (momapping,FILE_MAP_READ,0,0,0); + if momemoryHandle=nil then begin + raise EGGIOError.Create ('Cannot map file '+filename+' into memory. Reason: '+GetLastWinError); + end; + momemory:=momemoryHandle+offset; + {$endif} + {$ifdef linux} + // Read the whole file into memory + mofile:=TFileStream.Create (filename, fmOpenRead or fmShareDenyNone); + try + if size=0 then + size:=mofile.Size; + Getmem (momemoryHandle,size); + momemory:=momemoryHandle; + mofile.Seek(offset,soFromBeginning); + mofile.ReadBuffer(momemory^,size); + finally + FreeAndNil (mofile); + end; + {$endif} + + // Check the magic number + doswap:=False; + i:=CardinalInMem(momemory,0); + if (i <> $950412DE) and (i <> $DE120495) then + raise EGGIOError.Create('This file is not a valid GNU gettext mo file: ' + filename); + doswap := (i = $DE120495); + + + // Find the positions in the file according to the file format spec + CardinalInMem(momemory,4); // Read the version number, but don't use it for anything. + N:=CardinalInMem(momemory,8); // Get string count + O:=CardinalInMem(momemory,12); // Get offset of original strings + T:=CardinalInMem(momemory,16); // Get offset of translated strings + + // Calculate start conditions for a binary search + nn := N; + startindex := 1; + while nn <> 0 do begin + nn := nn shr 1; + startindex := startindex shl 1; + end; + startindex := startindex shr 1; + startstep := startindex shr 1; +end; + +destructor TMoFile.Destroy; +begin + {$ifdef mswindows} + UnMapViewOfFile (momemoryHandle); + CloseHandle (momapping); + CloseHandle (mo); + {$endif} + {$ifdef linux} + FreeMem (momemoryHandle); + {$endif} + inherited; +end; + +function TMoFile.gettext(const msgid: RawUtf8String;var found:boolean): RawUtf8String; +var + i, step: cardinal; + offset, pos: cardinal; + CompareResult:integer; + msgidptr,a,b:PAnsiChar; + abidx:integer; + size, msgidsize:integer; +begin + found:=false; + msgidptr:=PAnsiChar(msgid); + msgidsize:=length(msgid); + + // Do binary search + i:=startindex; + step:=startstep; + while true do begin + // Get string for index i + pos:=O+8*(i-1); + offset:=CardinalInMem (momemory,pos+4); + size:=CardinalInMem (momemory,pos); + a:=msgidptr; + b:=momemory+offset; + abidx:=size; + if msgidsize0 do begin + CompareResult:=integer(byte(a^))-integer(byte(b^)); + if CompareResult<>0 then + break; + dec (abidx); + inc (a); + inc (b); + end; + if CompareResult=0 then + CompareResult:=msgidsize-size; + if CompareResult=0 then begin // msgid=s + // Found the msgid + pos:=T+8*(i-1); + offset:=CardinalInMem (momemory,pos+4); + size:=CardinalInMem (momemory,pos); + SetString (Result,momemory+offset,size); + found:=True; + break; + end; + if step=0 then begin + // Not found + Result:=msgid; + break; + end; + if CompareResult<0 then begin // msgids + i := i + step; + if i > N then + i := N; + step := step shr 1; + end; + end; +end; + +var + param0:string; + +initialization + {$ifdef DXGETTEXTDEBUG} + {$ifdef MSWINDOWS} + MessageBox (0,'gnugettext.pas debugging is enabled. Turn it off before releasing this piece of software.','Information',MB_OK); + {$endif} + {$ifdef LINUX} + writeln (stderr,'gnugettext.pas debugging is enabled. Turn it off before releasing this piece of software.'); + {$endif} + {$endif} + {$ifdef FPC} + {$ifdef LINUX} + SetLocale(LC_ALL, ''); + SetCWidestringManager; + {$endif LINUX} + {$endif FPC} + if IsLibrary then begin + // Get DLL/shared object filename + SetLength (ExecutableFilename,300); + {$ifdef MSWINDOWS} + SetLength (ExecutableFilename,GetModuleFileName(FindClassHInstance(TGnuGettextInstance), PChar(ExecutableFilename), length(ExecutableFilename))); + {$else} + SetLength (ExecutableFilename,GetModuleFileName(0, PAnsiChar(ExecutableFilename), length(ExecutableFilename))); + {$endif} + end else + ExecutableFilename:=Paramstr(0); + FileLocator:=TFileLocator.Create; + FileLocator.Analyze; + ResourceStringDomainList:=TStringList.Create; + ResourceStringDomainList.Add(DefaultTextDomain); + ResourceStringDomainListCS:=TMultiReadExclusiveWriteSynchronizer.Create; + DefaultInstance:=TGnuGettextInstance.Create; + {$ifdef MSWINDOWS} + Win32PlatformIsUnicode := (Win32Platform = VER_PLATFORM_WIN32_NT); + {$endif} + + // replace Borlands LoadResString with gettext enabled version: + {$ifdef UNICODE} + HookLoadResString:=THook.Create (@system.LoadResString, @LoadResStringW); + {$else} + HookLoadResString:=THook.Create (@system.LoadResString, @LoadResStringA); + {$endif} + HookLoadStr:=THook.Create (@sysutils.LoadStr, @SysUtilsLoadStr); + HookFmtLoadStr:=THook.Create (@sysutils.FmtLoadStr, @SysUtilsFmtLoadStr); + param0:=lowercase(extractfilename(paramstr(0))); + if (param0<>'delphi32.exe') and (param0<>'kylix') and (param0<>'bds.exe') then + HookIntoResourceStrings (AutoCreateHooks,false); + param0:=''; + +finalization + FreeAndNil (DefaultInstance); + FreeAndNil (ResourceStringDomainListCS); + FreeAndNil (ResourceStringDomainList); + FreeAndNil (HookFmtLoadStr); + FreeAndNil (HookLoadStr); + FreeAndNil (HookLoadResString); + FreeAndNil (FileLocator); + +end. + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/de/LC_MESSAGES/xampp_control.mo b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/de/LC_MESSAGES/xampp_control.mo new file mode 100755 index 0000000000..03c7f6d896 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/de/LC_MESSAGES/xampp_control.mo differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/de/LC_MESSAGES/xampp_control.po b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/de/LC_MESSAGES/xampp_control.po new file mode 100755 index 0000000000..a0159c4b6d --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/de/LC_MESSAGES/xampp_control.po @@ -0,0 +1,2165 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: UDP Manager\n" +"POT-Creation-Date: 2011-05-10 15:09+0100\n" +"PO-Revision-Date: 2011-05-10 15:09+0100\n" +"Last-Translator: Steffen Strüber \n" +"Language-Team: Strueber-IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: dxgettext 1.2.1\n" +"X-Poedit-Language: English\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: iso-8859-1\n" +"X-Poedit-Basepath: C:\\Delphi\\xampp_control3\n" +"X-Poedit-SearchPath-0: .\n" + +#: uApache.pas:49 +#: uFileZilla.pas:108 +#: uMySQL.pas:43 +msgid "Executing \"%s\" \"%s\"" +msgstr "Führe aus: \"%s\" \"%s\"" + +#: uApache.pas:52 +#: uApache.pas:160 +#: uApache.pas:167 +#: uApache.pas:181 +#: uApache.pas:191 +#: uFileZilla.pas:40 +#: uFileZilla.pas:94 +#: uFileZilla.pas:123 +#: uFileZilla.pas:140 +#: uMain.pas:159 +#: uMain.pas:242 +#: uMain.pas:446 +#: uMercury.pas:136 +#: uMercury.pas:149 +#: uMySQL.pas:46 +#: uMySQL.pas:128 +#: uMySQL.pas:135 +#: uMySQL.pas:149 +#: uMySQL.pas:159 +msgid "Executing \"%s\"" +msgstr "Führe aus: \"%s\"" + +#: uApache.pas:59 +#: uFileZilla.pas:47 +#: uMySQL.pas:53 +msgid "Checking for service (name=\"%s\"): %s" +msgstr "Überprüfe Dienst (name=\"%s\"): %s" + +#: uApache.pas:71 +#: uFileZilla.pas:59 +#: uMercury.pas:87 +#: uMySQL.pas:64 +msgid "Initializing module..." +msgstr "Initialisiere Module..." + +#: uApache.pas:74 +#: uFileZilla.pas:63 +#: uMercury.pas:90 +#: uMySQL.pas:67 +msgid "Possible problem detected: file \"%s\" not found - run this program from your XAMPP root directory!" +msgstr "Mögliches Problem erkannt: Datei \"%s\" nicht gefunden - Führen Sie dieses Programm im xampp-Hauptverzeichnis aus!" + +#: uApache.pas:79 +#: uFileZilla.pas:68 +#: uMercury.pas:93 +#: uMySQL.pas:73 +msgid "Checking default ports..." +msgstr "Überprüfe Standard-Ports..." + +#: uApache.pas:85 +#: uFileZilla.pas:72 +#: uMercury.pas:100 +#: uMySQL.pas:77 +msgid "\"%s\" seems to be running on port %d?" +msgstr "\"%s\" scheint auf dem Port %d zu laufen?" + +#: uApache.pas:87 +#: uFileZilla.pas:74 +#: uMercury.pas:104 +#: uMySQL.pas:79 +msgid "Possible problem detected: Port %d in use by \"%s\"!" +msgstr "Mögliches Problem erkannt: Port %d in Benutzung von \"%s\"?" + +#: uApache.pas:106 +#: uMain.pas:506 +#: uMySQL.pas:99 +#: uMySQL.pas:113 +msgid "Executing %s %s" +msgstr "Führe aus: %s %s" + +#: uApache.pas:118 +#: uFileZilla.pas:93 +#: uMySQL.pas:98 +msgid "Installing service..." +msgstr "Installiere Dienst..." + +#: uApache.pas:119 +#: uApache.pas:133 +#: uApache.pas:148 +msgid "Executing \"%s %s\"" +msgstr "Führe aus: \"%s %s\"" + +#: uApache.pas:121 +#: uApache.pas:135 +#: uApache.pas:162 +#: uApache.pas:183 +#: uFileZilla.pas:96 +#: uFileZilla.pas:110 +#: uFileZilla.pas:125 +#: uFileZilla.pas:142 +#: uMySQL.pas:101 +#: uMySQL.pas:115 +#: uMySQL.pas:130 +#: uMySQL.pas:151 +msgid "Return code: %d" +msgstr "Rückgabecode: %d" + +#: uApache.pas:122 +#: uApache.pas:136 +#: uApache.pas:163 +#: uApache.pas:184 +#: uFileZilla.pas:97 +#: uFileZilla.pas:111 +#: uFileZilla.pas:126 +#: uFileZilla.pas:143 +#: uMySQL.pas:102 +#: uMySQL.pas:116 +#: uMySQL.pas:131 +#: uMySQL.pas:152 +msgid "There may be an error, return code: %d - %s" +msgstr "Es könnte ein Fehler aufgetreten sein, Rückgabecode: %d - %s" + +#: uApache.pas:132 +msgid "Uninstalling service..." +msgstr "Deinstalliere Dienst..." + +#: uApache.pas:158 +#: uFileZilla.pas:121 +#: uMySQL.pas:126 +msgid "Starting %s service..." +msgstr "Starte Dienst %s..." + +#: uApache.pas:165 +#: uMercury.pas:135 +#: uMySQL.pas:133 +msgid "Starting %s app..." +msgstr "Beende Programm %s..." + +#: uApache.pas:179 +#: uFileZilla.pas:138 +#: uMySQL.pas:147 +msgid "Stopping %s service..." +msgstr "Beende Dienst %s..." + +#: uApache.pas:189 +#: uMercury.pas:147 +#: uMySQL.pas:157 +msgid "Stopping" +msgstr "Beende" + +#: uApache.pas:195 +#: uMySQL.pas:163 +msgid "No PIDs found?!" +msgstr "Keine PIDs gefunden?!" + +#: uApache.pas:245 +#: uFileZilla.pas:194 +#: uMercury.pas:200 +#: uMySQL.pas:213 +msgid "running" +msgstr "läuft" + +#: uApache.pas:246 +#: uFileZilla.pas:195 +#: uMercury.pas:201 +#: uMySQL.pas:214 +msgid "stopped" +msgstr "beendet" + +#: uApache.pas:247 +#: uFileZilla.pas:196 +#: uMercury.pas:202 +#: uMySQL.pas:215 +msgid "Status change detected:" +msgstr "Statusänderung erkannt:" + +#: uApache.pas:253 +#: uFileZilla.pas:202 +#: uMercury.pas:208 +#: uMySQL.pas:221 +msgid "Stop" +msgstr "Stop" + +#: uApache.pas:257 +#: uFileZilla.pas:206 +#: uMercury.pas:212 +#: uMySQL.pas:225 +msgid "Start" +msgstr "Start" + +#: uApache.pas:265 +#: uFileZilla.pas:214 +#: uMercury.pas:220 +#: uMySQL.pas:233 +msgid "Autostart active: modul is already running - aborted" +msgstr "Autostart aktiv: Modul wird bereits ausgeführt - Abbruch" + +#: uApache.pas:267 +#: uFileZilla.pas:216 +#: uMercury.pas:222 +#: uMySQL.pas:235 +msgid "Autostart active: starting..." +msgstr "Autostart aktiv: Starte..." + +#: uFileZilla.pas:128 +#: uFileZilla.pas:145 +#: uMain.pas:251 +msgid "FileZilla must be run as service!" +msgstr "FileZilla kann nur als Dienst ausgeführt werden!" + +#: uMain.pas:266 +#: uMain.pas:359 +#: uMain.pas:393 +#: uMain.pas:403 +#: uMain.pas:528 +#: uMain.pas:529 +#: uMain.pas:540 +msgid "" +msgstr "" + +#: uMain.pas:284 +#: uMain.pas:317 +#: uMain.pas:413 +msgid "Services cant be installed or uninstalled while service is running!" +msgstr "Dienste können nicht (de-) installiert werden, solange diese ausgeführt werden!" + +#: uMain.pas:288 +#: uMain.pas:322 +#: uMain.pas:417 +msgid "Click Yes to uninstall the %s service" +msgstr "Klicke Ja, um den Dienst %s zu deinstallieren" + +#: uMain.pas:293 +#: uMain.pas:327 +#: uMain.pas:422 +msgid "Click Yes to install the \"%s\" service" +msgstr "Klicke Ja, um den Dienst %s zu installieren" + +#: uMain.pas:300 +#: uMain.pas:334 +#: uMain.pas:429 +msgid "Service was NOT (un)installed!" +msgstr "Dienst wurde NICHT (de-)installiert" + +#. WinXP +#. WinXP +#. WinXP +#: uMain.pas:302 +#: uMain.pas:336 +#: uMain.pas:431 +msgid "On windows security box you !!!MUST UNCHECK!!! that \"yes, cut service's balls\" checkbox!!!" +msgstr "Bei der Windows-Sicherheitsabfrage MUSS das Häkchen bei \"Ja, schneide dem Dienst die Eier ab\" ABGEWÄHLT werden!" + +#: uMain.pas:338 +msgid "Successful!" +msgstr "Erfolgreich!" + +#: uMain.pas:373 +msgid "Mercury cant be run as service!" +msgstr "Mercury kann nicht als Dienst ausgeführt werden!" + +#: uMain.pas:470 +msgid "User defined" +msgstr "Benutzerdefiniert" + +#: uMain.pas:560 +msgid "Initializing main" +msgstr "Initialisere main" + +#: uMain.pas:564 +msgid "Windows version: %s" +msgstr "Windows version: %s" + +#: uMain.pas:572 +msgid "Running as admin - good!" +msgstr "Laufe mit Administratorrechten - gut!" + +#: uMain.pas:574 +msgid "Running not as admin! This will work for all application stuff, but whenever you do" +msgstr "Programm läuft nicht mit Administratorrechten. Das funktioniert, aber wenn" + +#: uMain.pas:575 +msgid "something with services there will be a security dialogue! So think about running" +msgstr "irgendetwas mit Diensten gemacht werden soll, kommt eine Sicherheitsabfrage!" + +#: uMain.pas:576 +msgid "this application with administrator rights!" +msgstr "Daher sollte das Programm besser mit Administratorrechten ausgeführt werden!" + +#: uMain.pas:584 +msgid "Working with basedir: \"%s\"" +msgstr "Arbite mit Hauptverzeichnis: \"%s\"" + +#: uMain.pas:591 +msgid "Initializing moduls" +msgstr "Initialisiere Module" + +#: uMain.pas:599 +#: uMain.pas:603 +#: uMain.pas:607 +#: uMain.pas:611 +msgid "Enabling autostart for module \"%s\"" +msgstr "Aktiviere Autostart für Modul \"%s\"" + +#: uMain.pas:615 +msgid "Starting" +msgstr "Starte" + +#: uMain.pas:623 +msgid "Deinitializing moduls" +msgstr "Deinitialisiere Module" + +#: uMain.pas:628 +msgid "Deinitializing main" +msgstr "Deinitialisiere main" + +#: uNetstat.pas:220 +msgid "New listening socket: %s:%d" +msgstr "Neuer offener Socket: %s:%d" + +#: uNetstat.pas:235 +msgid "Listening socket closed: %s:%d" +msgstr "Offener Socket geschlossen: %s:%d" + +#: uNetstatTable.pas:109 +msgid "unknown program" +msgstr "unbekanntes Programm" + +#. hModuleSnap := INVALID_HANDLE_VALUE; +#: uProcesses.pas:115 +msgid "Creating PID-entry %d: %s" +msgstr "Erzeuge PID-Eintrag: %d: %s" + +#: uProcesses.pas:126 +msgid "Deleting PID-entry %d: %s" +msgstr "Lösche PID-Eintrag: %d: %s" + +#: uTools.pas:115 +#: uTools.pas:152 +msgid "Error" +msgstr "Fehler" + +#: uTools.pas:183 +msgid "Service" +msgstr "Dienst" + +#: uTools.pas:184 +msgid "Application" +msgstr "Programm" + +#. fConfig..Caption +#: uConfig.dfm:4 +msgid "Configuration of Control Panel" +msgstr "Konfiguration des Control Panels" + +#. fConfig..Font.Name +#. fConfig..GroupBox1..Label3..Font.Name +#. fConfigUserDefined..Font.Name +#. fConfigUserDefined..lConfig..Font.Name +#. fConfigUserDefined..lLogs..Font.Name +#. fHelp..Font.Name +#. fHelp..lAbout..Font.Name +#. fLanguage..Font.Name +#. fNetstat..Font.Name +#. fNetstat..Panel1..Font.Name +#. fNetstat..Panel2..Font.Name +#. fNetstat..Panel3..Font.Name +#: uConfig.dfm:11 +#: uConfig.dfm:249 +#: uConfigUserDefined.dfm:11 +#: uConfigUserDefined.dfm:47 +#: uConfigUserDefined.dfm:60 +#: uHelp.dfm:11 +#: uHelp.dfm:30 +#: uLanguage.dfm:13 +#: uNetstat.dfm:12 +#: uNetstat.dfm:90 +#: uNetstat.dfm:106 +#: uNetstat.dfm:122 +msgid "Tahoma" +msgstr "Tahoma" + +#. fConfig..Label1..Caption +#: uConfig.dfm:29 +msgid "Editor:" +msgstr "Editor:" + +#. fConfig..Label2..Caption +#: uConfig.dfm:36 +msgid "Browser (empty = system default)" +msgstr "Browser (leer = Systemvorgabe)" + +#. fConfig..bSave..Caption +#. fConfigUserDefined..bSave..Caption +#: uConfig.dfm:106 +#: uConfigUserDefined.dfm:70 +msgid "Save" +msgstr "Speichern" + +#. fConfig..bAbort..Caption +#. fConfigUserDefined..bAbort..Caption +#: uConfig.dfm:137 +#: uConfigUserDefined.dfm:103 +msgid "Abort" +msgstr "Über" + +#. fConfig..cbDebug..Caption +#: uConfig.dfm:211 +msgid "Show debug stuff" +msgstr "Debugdaten anzeigen" + +#. fConfig..cbDebugDetails....Items.Strings +#: uConfig.dfm:224 +msgid "Details" +msgstr "Details" + +#. fConfig..cbDebugDetails....Items.Strings +#: uConfig.dfm:225 +msgid "Very many details" +msgstr "Sehr viele Details" + +#. fConfig..GroupBox1..Caption +#: uConfig.dfm:233 +msgid " Autostart of modules " +msgstr " Module automatisch starten " + +#. fConfig..GroupBox1..Label3..Caption +#: uConfig.dfm:245 +msgid "Selected modules will be started on next launch of this program." +msgstr "Module auswählen, die bei Programmstart automatisch gestartet werden sollen." + +#. fConfig..GroupBox1..cbASApache..Caption +#. fConfigUserDefined..gbApache..Caption +#. fMain..gbModules..pApacheStatus..Caption +#: uConfig.dfm:259 +#: uConfigUserDefined.dfm:117 +#: uMain.dfm:1299 +msgid "Apache" +msgstr "Apache" + +#. fConfig..GroupBox1..cbASMySQL..Caption +#. fConfigUserDefined..gbMySQL..Caption +#. fMain..gbModules..pMySQLStatus..Caption +#: uConfig.dfm:267 +#: uConfigUserDefined.dfm:141 +#: uMain.dfm:1386 +msgid "MySQL" +msgstr "MySQL" + +#. fConfig..GroupBox1..cbASFileZilla..Caption +#. fConfigUserDefined..gbFileZilla..Caption +#. fMain..gbModules..pFileZillaStatus..Caption +#: uConfig.dfm:275 +#: uConfigUserDefined.dfm:165 +#: uMain.dfm:1396 +msgid "FileZilla" +msgstr "FileZilla" + +#. fConfig..GroupBox1..cbASMercury..Caption +#. fConfigUserDefined..gbMercury..Caption +#. fMain..gbModules..pMercuryStatus..Caption +#: uConfig.dfm:283 +#: uConfigUserDefined.dfm:189 +#: uMain.dfm:1406 +msgid "Mercury" +msgstr "Mercury" + +#. fConfig..cbCheckDefaultPorts..Caption +#: uConfig.dfm:293 +msgid "Check default ports on startup" +msgstr "Überprüfe Standard-Ports beim Start" + +#. fConfig..BitBtn1..Caption +#: uConfig.dfm:302 +msgid "Change language" +msgstr "Change language" + +#. fConfig..bConfigUserdefined..Caption +#: uConfig.dfm:314 +msgid "User defined files" +msgstr "Benutzerdefinierte Dateien" + +#. fConfig..OpenDialog1..DefaultExt +#: uConfig.dfm:323 +msgid "exe" +msgstr "exe" + +#. fConfig..OpenDialog1..Filter +#: uConfig.dfm:324 +msgid "Executables (*.exe)|*.exe|All files (*.*)|*.*" +msgstr "Programme (*.exe)|*.exe|Ale Dateien (*.*)|*.*" + +#. fConfigUserDefined..Caption +#: uConfigUserDefined.dfm:4 +msgid "User-defined log/config-files" +msgstr "Benutzerdefinierte Log/Konfiguirations-dateien" + +#. fConfigUserDefined..lHeader1....Caption +#: uConfigUserDefined.dfm:29 +msgid "Enter user defined files. Files must be relative to xampp-basedir!" +msgstr "Geben Sie benutzerdefinierte Dateinamen relativ zum xampp-Basisverzeichnis ein!" + +#. fConfigUserDefined..lHeader2..Caption +#: uConfigUserDefined.dfm:36 +msgid "Example: \"apache\\conf\\extra\\httpd-info.conf\"" +msgstr "Beispiel: \"apache\\conf\\extra\\httpd-info.conf\"" + +#. fConfigUserDefined..lConfig..Caption +#. fMain..bConfig..Caption +#. fMain..gbModules..bApacheConfig..Caption +#. fMain..gbModules..bMySQLConfig..Caption +#. fMain..gbModules..bFileZillaConfig..Caption +#. fMain..gbModules..bMercuryConfig..Caption +#: uConfigUserDefined.dfm:43 +#: uMain.dfm:1033 +#: uMain.dfm:1308 +#: uMain.dfm:1330 +#: uMain.dfm:1352 +#: uMain.dfm:1374 +msgid "Config" +msgstr "Konfig" + +#. fConfigUserDefined..lLogs..Caption +#. fMain..gbModules..bApacheLogs..Caption +#. fMain..gbModules..bMySQLLogs..Caption +#. fMain..gbModules..bFileZillaLogs..Caption +#. fMain..gbModules..bMercurylogs..Caption +#: uConfigUserDefined.dfm:56 +#: uMain.dfm:1319 +#: uMain.dfm:1341 +#: uMain.dfm:1363 +#: uMain.dfm:1445 +msgid "Logs" +msgstr "Logs" + +#. fHelp..Caption +#. fMain..bHelp..Caption +#: uHelp.dfm:4 +#: uMain.dfm:1493 +msgid "Help" +msgstr "Hilfe" + +#. fHelp..lAbout..Caption +#: uHelp.dfm:26 +msgid "programmed by Steffen Strueber" +msgstr "programmiert von Steffen Strüber" + +#. fHelp..Label1..Caption +#: uHelp.dfm:39 +msgid "Uhm, did this help? :-)" +msgstr "Ehm, hat das geholfen? :-)" + +#. fHelp..BitBtn1..Caption +#: uHelp.dfm:47 +msgid "Close" +msgstr "Schließen" + +#. fLanguage..Caption +#: uLanguage.dfm:5 +msgid "Konfiguration" +msgstr "Configuration" + +#. fLanguage..bOkay..Caption +#: uLanguage.dfm:167 +msgid "OK" +msgstr "OK" + +#. fMain..Caption +#. fMain..lHeader..Caption +#: uMain.dfm:4 +#: uMain.dfm:1003 +msgid "XAMPP Control Panel v" +msgstr "XAMPP Control Panel v" + +#. fMain..Font.Name +#. fMain..lHeader..Font.Name +#. fMain..reLog..Font.Name +#. fMain..gbModules..lPIDs..Font.Name +#. fMain..gbModules..lPorts..Font.Name +#. fMain..gbModules..Label1..Font.Name +#. fMain..gbModules..Label2..Font.Name +#. fMain..gbModules..Label3..Font.Name +#: uMain.dfm:12 +#: uMain.dfm:1007 +#: uMain.dfm:1020 +#: uMain.dfm:1067 +#: uMain.dfm:1081 +#: uMain.dfm:1171 +#: uMain.dfm:1186 +#: uMain.dfm:1200 +msgid "Verdana" +msgstr "Verdana" + +#. fMain..bSCM..Caption +#: uMain.dfm:1044 +msgid "Win-Services" +msgstr "Win-Dienste" + +#. fMain..gbModules..Caption +#: uMain.dfm:1055 +msgid " Modules " +msgstr " Module " + +#. fMain..gbModules..lPIDs..Caption +#: uMain.dfm:1063 +msgid "PID(s)" +msgstr "PID(s)" + +#. fMain..gbModules..lPorts..Caption +#: uMain.dfm:1077 +msgid "Port(s)" +msgstr "Port(s)" + +#. fMain..gbModules..Label2..Caption +#: uMain.dfm:1182 +msgid "Module" +msgstr "Modul" + +#. fMain..gbModules..Label3..Caption +#: uMain.dfm:1196 +msgid "Action" +msgstr "Aktion" + +#. fMain..gbModules..bApacheAdmin..Caption +#. fMain..gbModules..bMySQLAdmin..Caption +#. fMain..gbModules..bFileZillaAdmin..Caption +#. fMain..gbModules..bMercuryAdmin..Caption +#: uMain.dfm:1220 +#: uMain.dfm:1242 +#: uMain.dfm:1264 +#: uMain.dfm:1287 +msgid "Admin" +msgstr "Admin" + +#. fMain..bQuit..Caption +#. fMain..puSystray..miTerminate..Caption +#: uMain.dfm:1464 +#: uMain.dfm:2514 +msgid "Quit" +msgstr "Ende" + +#. fMain..bExplorer..Caption +#: uMain.dfm:1504 +msgid "Explorer" +msgstr "Explorer" + +#. fMain..bNetstat..Caption +#: uMain.dfm:1515 +msgid "Netstat" +msgstr "Netstat" + +#. fMain..puSystray..miShowHide..Caption +#: uMain.dfm:2506 +msgid "Show / Hide" +msgstr "Anzeigen / verstecken" + +#. fMain..puSystray..N1..Caption +#: uMain.dfm:2511 +msgid "-" +msgstr "-" + +#. fNetstat..Caption +#: uNetstat.dfm:5 +msgid "Netstat - TCP Listening sockets" +msgstr "Netstat - Offene TCP Sockets" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:33 +msgid "Address" +msgstr "Adresse" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:38 +msgid "Port" +msgstr "Port" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:42 +msgid "PID" +msgstr "PID" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:45 +msgid "Name" +msgstr "Name" + +#. fNetstat..BitBtn1..Caption +#: uNetstat.dfm:65 +msgid "Refresh" +msgstr "Aktualisieren" + +#. fNetstat..Panel1..Caption +#: uNetstat.dfm:85 +msgid "Active socket" +msgstr "Offener Socket" + +#. fNetstat..Panel2..Caption +#: uNetstat.dfm:101 +msgid "Old socket" +msgstr "Alter Socket" + +#. fNetstat..Panel3..Caption +#: uNetstat.dfm:117 +msgid "New socket" +msgstr "Neuer Socket" + +#~ msgid "Apache (httpd.conf)" +#~ msgstr "Apache (httpd.conf)" + +#~ msgid "apache\\conf\\httpd.conf" +#~ msgstr "apache\\conf\\httpd.conf" + +#~ msgid "PHP (php.ini)" +#~ msgstr "PHP (php.ini)" + +#~ msgid "More" +#~ msgstr "Mehr" + +#~ msgid "apache\\conf\\extra\\httpd-vhosts.conf" +#~ msgstr "apache\\conf\\extra\\httpd-vhosts.conf" + +#~ msgid "apache\\conf\\extra\\httpd-xampp.conf" +#~ msgstr "apache\\conf\\extra\\httpd-xampp.conf" + +#~ msgid "apache\\conf\\extra\\httpd-ssl.conf" +#~ msgstr "apache\\conf\\extra\\httpd-ssl.conf" + +#~ msgid "apache\\conf\\extra\\httpd-default.conf" +#~ msgstr "apache\\conf\\extra\\httpd-default.conf" + +#~ msgid "apache\\conf\\extra\\httpd-dav.conf" +#~ msgstr "apache\\conf\\extra\\httpd-dav.conf" + +#~ msgid "access.log" +#~ msgstr "access.log" + +#~ msgid "error.log" +#~ msgstr "error.log" + +#~ msgid "XAMPP Control Panel Application v3.0" +#~ msgstr "XAMPP Control Panel Application v3.0" + +#~ msgid "SCV" +#~ msgstr "Dienst" + +#, fuzzy +#~ msgid "Anzeigen" +#~ msgstr "Display" + +#, fuzzy +#~ msgid "Beenden" +#~ msgstr "Terminating..." + +#~ msgid "Graupner GmbH && Co. KG" +#~ msgstr "Graupner GmbH && Co. KG" + +#~ msgid "Henriettenstraße 94-96" +#~ msgstr "Henriettenstraße 94-96" + +#~ msgid "73230 Kirchheim/Teck" +#~ msgstr "73230 Kirchheim/Teck" + +#~ msgid "Vertretungsberechtigte Geschäftsführer" +#~ msgstr "Authorised Representative Managing Directors" + +#~ msgid "Hans Graupner, Stefan Graupner" +#~ msgstr "Hans Graupner, Stefan Graupner" + +#~ msgid "Telefon/Fax/E-Mail-Adresse" +#~ msgstr "Phone/Fax/E-mail address" + +#~ msgid "Telefon: 07021 722-0" +#~ msgstr "Phone: +49 7021 722-0" + +#~ msgid "Fax: 07021 722-200" +#~ msgstr "Fax: +49 7021 722-200" + +#~ msgid "Email: info@graupner.de" +#~ msgstr "Email: info@graupner.de" + +#~ msgid "Registergericht:" +#~ msgstr "Court of registration:" + +#~ msgid "Amtsgericht Stuttgart HRA 230523" +#~ msgstr "Stuttgart District Court, Trade Register No. 230523" + +#~ msgid "Umsatzsteueridentifikationsnummer" +#~ msgstr "VAT identification number" + +#~ msgid "DE 145929079" +#~ msgstr "DE 145929079" + +#~ msgid "Akku Zellen (x %.1f V)" +#~ msgstr "Battery cells (x %.1f V)" + +#~ msgid "neu" +#~ msgstr "new" + +#~ msgid "Speicherplatz: %s" +#~ msgstr "Battery number: %s" + +#~ msgid "==> %.1f V" +#~ msgstr "==> %.1f V" + +#~ msgid "Standardwert (für Akku-Typ=%s)" +#~ msgstr "Default (for Battery type=%s)" + +#~ msgid "Serieller Port konnte nicht geöffnet werden." +#~ msgstr "Serial port could not be opened" + +#~ msgid "Leerlauf" +#~ msgstr "Idle" + +#~ msgid "Laden" +#~ msgstr "Charge" + +#~ msgid "Entladen" +#~ msgstr "Discharge" + +#~ msgid "Wartezeit" +#~ msgstr "Delay" + +#~ msgid "Hitze-Schutz" +#~ msgstr "Pause time (Charger too hot)" + +#~ msgid "Vorgang abgeschlossen" +#~ msgstr "Operation completed" + +#~ msgid "Fehler" +#~ msgstr "Error" + +#~ msgid "Balancen" +#~ msgstr "Balance" + +#~ msgid "Heizung/Netzteil" +#~ msgstr "Tyre Heater" + +#~ msgid "DC-Motor" +#~ msgstr "Motor mode" + +#~ msgid "Lagermodus: Laden" +#~ msgstr "Storemode: Charge" + +#~ msgid "Lagermodus: Entladen" +#~ msgstr "Storemode: Discharge" + +#~ msgid "Schreibe" +#~ msgstr "Writing" + +#~ msgid "Lese" +#~ msgstr "Reading" + +#~ msgid "" +#~ "Achtung, die Firmware Ihres Ladergeräts ist in Bezug auf die PC-" +#~ "kommunikation fehlerhaft!" +#~ msgstr "" +#~ "Attention, the firmware of your charger has some errors related to " +#~ "communication to the PC!" + +#~ msgid "" +#~ "Um dieses Programm mit Ihrem Ladegrät nutzen zu können, müssen Sie die " +#~ "Firmware updaten." +#~ msgstr "" +#~ "To use this program with your charger, you have to upgrade the chargers " +#~ "firmware ." + +#~ msgid "" +#~ "Besuchen Sie dazu die Homepage von Graupner bzw. klicken Sie im Programm " +#~ "auf das Ladegerät UDP 40, um automatisch die entsprechende Seite zu laden." +#~ msgstr "" +#~ "Visit the homepage of Graupner or click on the icon of UDP 40 in this " +#~ "program." + +#~ msgid "" +#~ "Das Programm zum Updaten der Firmware finden Sie evtl. nur auf der Seite " +#~ "des UDP 50!" +#~ msgstr "" +#~ "The program to update the firmware can be found on the page of the UDP 50." + +#~ msgid "K" +#~ msgstr "Ch" + +#~ msgid " Linke Seite " +#~ msgstr " Left side " + +#~ msgid "Das Programm muss neu gestartet werden. Weiter?" +#~ msgstr "The program has to be restarted. Proceed?" + +#~ msgid "--- Automatisch erkannte COM-Ports ---" +#~ msgstr "--- Automatic detected COM-Ports ---" + +#~ msgid "--- Manuelle Eingabe des COM-Ports ---" +#~ msgstr "--- Manual selection of COM-Ports ---" + +#~ msgid "Fullscreen" +#~ msgstr "Fullscreen" + +#~ msgid "links" +#~ msgstr "left" + +#~ msgid "Spannung" +#~ msgstr "Voltage" + +#~ msgid "rechts" +#~ msgstr "right" + +#~ msgid "Temperatur" +#~ msgstr "Temperature" + +#~ msgid "Werte Daten aus..." +#~ msgstr "Interpreting data..." + +#~ msgid "Keine Daten geladen!" +#~ msgstr "No data loaded!" + +#~ msgid "Exportiert am %s" +#~ msgstr "Exported on %s" + +#~ msgid "Messpunkte - Intervall = %d Sekunden" +#~ msgstr "measuring interval = %d seconds" + +#~ msgid "Spannung [V]" +#~ msgstr "Voltage [V]" + +#~ msgid "Strom [mA]" +#~ msgstr "Current [mA]" + +#~ msgid "Temperatur [C°]" +#~ msgstr "Temperature [°C]" + +#~ msgid "Links" +#~ msgstr "Left" + +#~ msgid "Rechts" +#~ msgstr "Right" + +#~ msgid "Intervall" +#~ msgstr "Interval" + +#~ msgid "Sek" +#~ msgstr "Sec" + +#~ msgid "Vollbild" +#~ msgstr "Fullscreen" + +#~ msgid "Schließen" +#~ msgstr "Close" + +#~ msgid "Das Dateiformat ist nicht mit dieser Version kompatibel!" +#~ msgstr "The file format is not compatible with this version!" + +#~ msgid "Lade Daten..." +#~ msgstr "Loading data..." + +#~ msgid "Schreibe Daten..." +#~ msgstr "Writing data..." + +#~ msgid "Daten gespeichert!" +#~ msgstr "Data saved!" + +#~ msgid "Aktuell" +#~ msgstr "Current" + +#~ msgid "Zellenspannung" +#~ msgstr "Cell voltage" + +#~ msgid "Gesamtspannung" +#~ msgstr "Total voltage" + +#~ msgid "Ladung" +#~ msgstr "Kapazität" + +#~ msgid "K%d: " +#~ msgstr "Ch%d:" + +#~ msgid "Zelle %d" +#~ msgstr "Cell %d" + +#~ msgid "Zyklus" +#~ msgstr "Cycle" + +#~ msgid "Uhrzeit" +#~ msgstr "Time" + +#~ msgid "Kanal" +#~ msgstr "Channel" + +#~ msgid "Zelle" +#~ msgstr "Cell" + +#~ msgid "UDP Manager v%s - Copyright by Graupner GmbH 2009-2010" +#~ msgstr "UDP Manager v%s - Copyright by Graupner GmbH 2009-2010" + +#~ msgid "UDP Manager v%s" +#~ msgstr "UDP Manager v%s" + +#~ msgid "Einzel-Speicher" +#~ msgstr "Single-Memory" + +#~ msgid "Multi-Speicher" +#~ msgstr "Multi-Memory" + +#~ msgid "Live-Daten" +#~ msgstr "Live-Data" + +#~ msgid "Alle Dateien" +#~ msgstr "All files" + +#~ msgid "Sie müssen eine gültige Zahl als Speichernummer angeben!" +#~ msgstr "You must enter a valid memory number!" + +#~ msgid "Erlaubte Speichernummer: 1 bis %d" +#~ msgstr "Allowed memory number: 1 to %d" + +#~ msgid "Lade Speicher..." +#~ msgstr "Loading memory..." + +#~ msgid "Starte PC-Mode" +#~ msgstr "Starting PC-Mode" + +#~ msgid "Verlasse PC-Mode" +#~ msgstr "Leaving PC-Mode" + +#~ msgid "Keine vollständigen Daten geladen!" +#~ msgstr "No complete data loaded!" + +#~ msgid "" +#~ "Quellspeicher-Nummer und Zielspeicher-Nummer sind unterschiedlich. Weiter?" +#~ msgstr "Source-number and destination-number are not equal. Proceed?" + +#~ msgid "%d/%d (%.0f %%)" +#~ msgstr "%d/%d (%.0f %%)" + +#~ msgid "Fehler!" +#~ msgstr "Error!" + +#~ msgid "Sie müssen alle Daten eingeben!" +#~ msgstr "You have to fill out all data fields!" + +#~ msgid "Gerät konnte nicht erkannt werden!" +#~ msgstr "Charger could not be detected!" + +#~ msgid "Gerät nicht bereit. Beenden Sie alle Ladevorgänge etc.!" +#~ msgstr "Charger not ready. Finish all operations like charging etc!" + +#~ msgid "Fehler: %s" +#~ msgstr "Error: %s" + +#~ msgid "unbekannt" +#~ msgstr "unknown" + +#~ msgid "COM-Port geöffnet" +#~ msgstr "COM-Port opened" + +#~ msgid "COM-Port geschlossen" +#~ msgstr "COM-Port closed" + +#~ msgid "Unbekanntes Gerät" +#~ msgstr "Unknown device" + +#~ msgid "Wähle Sheet aus" +#~ msgstr "Selecting sheet" + +#~ msgid "Bereite Format vor" +#~ msgstr "Preparing format" + +#~ msgid "%d / %d, %d%% fertig" +#~ msgstr "%d / %d, %d%% done" + +#~ msgid "Excel konnte nicht gestartet werden!" +#~ msgstr "Excel could not be started!" + +#~ msgid "Eventuell ist kein Excel installiert?" +#~ msgstr "Maybe there is no MS-Excel installed?" + +#~ msgid "Starte Excel" +#~ msgstr "Starting Excel" + +#~ msgid "fAbout" +#~ msgstr "fAbout" + +#~ msgid "UDP Manager" +#~ msgstr "UDP Manager" + +#~ msgid "Arial" +#~ msgstr "Arial" + +#~ msgid "lLegalStuff" +#~ msgstr "lLegalStuff" + +#~ msgid "Entwickelt von" +#~ msgstr "Developed by" + +#~ msgid "Dipl.-Wirt.-Inf Steffen Strüber" +#~ msgstr "Dipl.-Wirt.-Inf Steffen Strüber" + +#~ msgid " Akkuinformation " +#~ msgstr " Battery information " + +#~ msgid "Akku Typ" +#~ msgstr "Battery type" + +#~ msgid "Akku Zellen" +#~ msgstr "Battery cells" + +#~ msgid "Akku Kapazität" +#~ msgstr "Battery capacity" + +#~ msgid "Inbetriebnahme" +#~ msgstr "Beginning of operation" + +#~ msgid "Akkuname (16 Zeichen)" +#~ msgstr "Battery name (16 charachters)" + +#~ msgid "Speicherplatz: 0" +#~ msgstr "Memory: 0" + +#~ msgid "mAh" +#~ msgstr "mAh" + +#~ msgid "=> 00.0V" +#~ msgstr "=> 00.0V" + +#~ msgid "Info: Anzahl Zyklen" +#~ msgstr "Info: Number of cycles" + +#~ msgid "NiCd" +#~ msgstr "NiCd" + +#~ msgid "NiMH" +#~ msgstr "NiMH" + +#~ msgid "LiIo" +#~ msgstr "LiIo" + +#~ msgid "LiPo" +#~ msgstr "LiPo" + +#~ msgid "LiFe" +#~ msgstr "LiFe" + +#~ msgid "Pb" +#~ msgstr "Pb" + +#~ msgid "eAkkuName" +#~ msgstr "eAkkuName" + +#~ msgid " Laden " +#~ msgstr " Load " + +#~ msgid "Ladestrom" +#~ msgstr "Charge current" + +#~ msgid "Peak Empfindlichkeit" +#~ msgstr "Peak sensibility" + +#~ msgid "Peak Verzögerung" +#~ msgstr "Peak delay" + +#~ msgid "Abschalttemperatur" +#~ msgstr "Cut-off temperature" + +#~ msgid "Erhaltungsstrom" +#~ msgstr "Trickle current" + +#~ msgid "Max Kapazität" +#~ msgstr "Max capacity" + +#~ msgid "%" +#~ msgstr "%" + +#~ msgid "Keine Spannnungsänderung" +#~ msgstr "No voltage change" + +#~ msgid "RePeak Pause" +#~ msgstr "RePeak pause" + +#~ msgid "RePeak Zyklen" +#~ msgstr "RePeak cylces" + +#~ msgid "Sicherheitstimer" +#~ msgstr "Security timer" + +#~ msgid "Ladespannung" +#~ msgstr "Charge voltage" + +#~ msgid "mA" +#~ msgstr "mA" + +#~ msgid "mV / Zelle" +#~ msgstr "mV / Zelle" + +#~ msgid "°C" +#~ msgstr "°C" + +#~ msgid "V / Zelle" +#~ msgstr "V / Zelle" + +#~ msgid "Lagerspannung" +#~ msgstr "Store voltage" + +#~ msgid "550 = AUS, 0=AUTO" +#~ msgstr "550 = OFF, 0=AUTO" + +#~ msgid "Aus" +#~ msgstr "Out" + +#~ msgid "An" +#~ msgstr "On" + +#~ msgid "905 = AUS" +#~ msgstr "905 = OFF" + +#~ msgid "155 = AUS" +#~ msgstr "155 = OFF" + +#~ msgid "Wert" +#~ msgstr "Value" + +#~ msgid "AUS" +#~ msgstr "OFF" + +#~ msgid "AUTO" +#~ msgstr "AUTO" + +#~ msgid " Zyklus " +#~ msgstr "Cycle" + +#~ msgid "Pause nach Entladen" +#~ msgstr "Pause after discharge" + +#~ msgid "Pause nach Laden" +#~ msgstr "Pause after charge" + +#~ msgid "Anzahl Zyklen" +#~ msgstr "Number of cycles" + +#~ msgid "Zyklus Reihenfolge" +#~ msgstr "Cycle order" + +#~ msgid "E:L->E" +#~ msgstr "D:C->D" + +#~ msgid "L->E" +#~ msgstr "C->D" + +#~ msgid "E->L" +#~ msgstr "D->C" + +#~ msgid " Entladen " +#~ msgstr " Discharge " + +#~ msgid "Entladestrom" +#~ msgstr "Disch. current" + +#~ msgid "Abschaltspannung" +#~ msgstr "Cut-off voltage" + +#~ msgid "Aschalttemperatur [°C]" +#~ msgstr "Cut-off temperature" + +#~ msgid "Max. Kapazität" +#~ msgstr "Max. capacity" + +#~ msgid "Balancerspannnung" +#~ msgstr "Balancer voltage" + +#~ msgid "V" +#~ msgstr "V" + +#~ msgid "105 = AUS" +#~ msgstr "105 = OFF" + +#~ msgid "Fertig-Melodie" +#~ msgstr "Finish melody" + +#~ msgid "LCD Kontrast" +#~ msgstr "LCD contrast" + +#~ msgid "Fertig-Melodie Dauer" +#~ msgstr "Finish melody duration" + +#~ msgid "Rollen" +#~ msgstr "Roll" + +#~ msgid "Letzte" +#~ msgstr "Last" + +#~ msgid "5 Sek" +#~ msgstr "5 Sec" + +#~ msgid "15 Sek" +#~ msgstr "15 Sec" + +#~ msgid "1 Min" +#~ msgstr "1 Min" + +#~ msgid " Allgemein " +#~ msgstr " Common " + +#~ msgid "Temperaturanzeige" +#~ msgstr "Temperature view" + +#~ msgid "Tastenton" +#~ msgstr "Key sound" + +#~ msgid "Netzteil / Batterie" +#~ msgstr "Powersupply / battery" + +#~ msgid "Zeitdarstellung" +#~ msgstr "Timeformat" + +#~ msgid "Aktuelles Datum" +#~ msgstr "Current date" + +#~ msgid "Aktuelle Zeit" +#~ msgstr "Current time" + +#~ msgid "A" +#~ msgstr "A" + +#~ msgid "Name Ladegerät (16 Zeichen)" +#~ msgstr "Name charging device" + +#~ msgid "°F" +#~ msgstr "°F" + +#~ msgid "Englisch" +#~ msgstr "English" + +#~ msgid "Deutsch" +#~ msgstr "German" + +#~ msgid "Französisch" +#~ msgstr "French" + +#~ msgid "Italienisch" +#~ msgstr "Italian" + +#~ msgid "12 H" +#~ msgstr "12 H" + +#~ msgid "24 H" +#~ msgstr "24 H" + +#~ msgid "eLoaderName" +#~ msgstr "eLoaderName" + +#~ msgid "PC-Zeit beim Schreiben benutzen" +#~ msgstr "Use Computer-time on write" + +#~ msgid "Netzteil2 / Batterie" +#~ msgstr "Powersupply2 / battery" + +#~ msgid "Lastverteilung" +#~ msgstr "Lload distribution" + +#~ msgid " Rechte Seite " +#~ msgstr " Right side " + +#~ msgid "COM-Port" +#~ msgstr "COM-Port" + +#~ msgid "Abbruch" +#~ msgstr "Cancel" + +#~ msgid "USB-Treiber" +#~ msgstr "USB-driver" + +#~ msgid "Debug Daten serielle Schnittstelle" +#~ msgstr "Debug data serial device" + +#~ msgid "Courier New" +#~ msgstr "Courier New" + +#~ msgid "mDebug" +#~ msgstr "mDebug" + +#~ msgid "In Zwischenspeicher kopieren" +#~ msgstr "Copy to clipboard" + +#~ msgid "Protokollierung pausieren" +#~ msgstr "Suspend logging" + +#~ msgid "Gerät neu starten" +#~ msgstr "Restart Device" + +#~ msgid "COM1" +#~ msgstr "COM1" + +#~ msgid "Restdauer" +#~ msgstr "Remaining time" + +#~ msgid "Berechne" +#~ msgstr "Calculating" + +#~ msgid "Restdauer: %d Sekunden" +#~ msgstr "Remaining time: %d seconds" + +#~ msgid "Buffer: %s/%d Bytes" +#~ msgstr "Buffer: %s/%d Bytes" + +#~ msgid "Dateiendung muss .BMP oder .JPG sein!" +#~ msgstr "Extension must be .BMP or .JPG!" + +#~ msgid "\"%s\" erfolgreich gespeichert!" +#~ msgstr "\"%s\" saves successfully!" + +#~ msgid "Daten" +#~ msgstr "Data" + +#~ msgid "Speicher %d/%d (%.0f %%)" +#~ msgstr "Memory %d/%d (%.0f %%)" + +#~ msgid "Schreibe Speicher..." +#~ msgstr "Writing memory..." + +#~ msgid "Fehler: " +#~ msgstr "Error: " + +#~ msgid "Länge der gelesenen Daten ungültig!" +#~ msgstr "Length mismatch of read data!" + +#~ msgid "" +#~ "Überprüfen Sie die COM-Port Einstellung und stellen Sie sicher, dass das " +#~ "Ladegerät zur Zeit nicht lädt!" +#~ msgstr "" +#~ "Recheck your COM-Port settings and ensure, that the charger is not busy!" + +#~ msgid "Interner Fehler: Noch zu parsende Daten vorhanden!" +#~ msgstr "Internal error: More data remaining!" + +#~ msgid "Namen" +#~ msgstr "Names" + +#~ msgid "Daten 1" +#~ msgstr "Daten 1" + +#~ msgid "Daten 2" +#~ msgstr "Daten 2" + +#~ msgid "Kein Treiber gefundenn!" +#~ msgstr "No driver found!" + +#~ msgid "Speicherplatz: %s (%d)" +#~ msgstr "Battery number: %s (%d)" + +#~ msgid "Speicherplatz" +#~ msgstr "Memory" + +#~ msgid "fFullscreen" +#~ msgstr "fFullscreen" + +#~ msgid "Temperatur [°C]" +#~ msgstr "Temperature [°C]" + +#~ msgid "Tabelle" +#~ msgstr "Table" + +#~ msgid "TChart" +#~ msgstr "TChart" + +#~ msgid "SeriesVoltageLeft" +#~ msgstr "SeriesVoltageLeft" + +#~ msgid "X" +#~ msgstr "X" + +#~ msgid "Y" +#~ msgstr "Y" + +#~ msgid "SeriesVoltageRight" +#~ msgstr "SeriesVoltageRight" + +#~ msgid "SeriesCurrentLeft" +#~ msgstr "SeriesCurrentLeft" + +#~ msgid "SeriesCurrentRight" +#~ msgstr "SeriesCurrentRight" + +#~ msgid "SeriesTempLeft" +#~ msgstr "SeriesTempLeft" + +#~ msgid "SeriesTempRight" +#~ msgstr "SeriesTempRight" + +#~ msgid "Excel-Export" +#~ msgstr "Excel-Export" + +#~ msgid "lDataPoints" +#~ msgstr "lDataPoints" + +#~ msgid "lFrom" +#~ msgstr "lFrom" + +#~ msgid "lTo" +#~ msgstr "lTo" + +#~ msgid "Legende" +#~ msgstr "Legend" + +#~ msgid "Datenpunkte" +#~ msgstr "Datapoints" + +#~ msgid "Alle Kanäle" +#~ msgstr "All channels" + +#~ msgid "Optionen" +#~ msgstr "Options" + +#~ msgid "Grafik speichern" +#~ msgstr "Save graphic" + +#~ msgid "Von:" +#~ msgstr "From:" + +#~ msgid "Bis:" +#~ msgstr "To:" + +#~ msgid "Status:" +#~ msgstr "Status:" + +#~ msgid "Modus:" +#~ msgstr "Mode:" + +#~ msgid "Punkte:" +#~ msgstr "Points:" + +#~ msgid "Neu" +#~ msgstr "New" + +#~ msgid "Daten laden" +#~ msgstr "Load data" + +#~ msgid "Daten speichern" +#~ msgstr "Save data" + +#~ msgid "Linienstärke" +#~ msgstr "Line width" + +#~ msgid "Diagramm-Titel" +#~ msgstr "Diagram title" + +#~ msgid "Durchschnitt" +#~ msgstr "Average" + +#~ msgid "Ladung [mAh]" +#~ msgstr "Capacity [mAh]" + +#~ msgid "Alle Graphen" +#~ msgstr "All Graphs" + +#~ msgid "Übersicht" +#~ msgstr "Overview" + +#~ msgid "PanelPCMain" +#~ msgstr "PanelPCMain" + +#~ msgid "Warte auf Daten..." +#~ msgstr "Waiting for data..." + +#~ msgid "#,##0.00" +#~ msgstr "#,##0.00" + +#~ msgid "#,##0.000" +#~ msgstr "#,##0.000" + +#~ msgid "Strom [mA] / Temperatur [°C]" +#~ msgstr "Current [mA] / Temperature [°C]" + +#~ msgid "#,##0.0" +#~ msgstr "#,##0.0" + +#~ msgid "Spannungen" +#~ msgstr "Voltages" + +#~ msgid "Strom/Temp" +#~ msgstr "Current/Temp" + +#~ msgid "jpg" +#~ msgstr "jpg" + +#~ msgid "" +#~ "Alle (*.bmp;*.jpg)|*.jpg;*.bmp|JPEG-Grafikdatei (*.jpg)|*.jpg|Bitmaps (*." +#~ "bmp)|*.bmp" +#~ msgstr "" +#~ "All (*.bmp;*.jpg)|*.jpg;*.bmp|JPEG-file (*.jpg)|*.jpg|Bitmaps (*.bmp)|*." +#~ "bmp" + +#~ msgid "Aktuell: 10 x 10" +#~ msgstr "Current: 10 x 10" + +#~ msgid "Größe: 800 x 600" +#~ msgstr "Size: 800 x 600" + +#~ msgid "Größe: 1024 x 768" +#~ msgstr "Size: 1024 x 768" + +#~ msgid "Größe: 1920 x 1200" +#~ msgstr "Size: 1920 x 1200" + +#~ msgid "Größe: 4000 x 2000" +#~ msgstr "Size: 4000 x 2000" + +#~ msgid "GroupBox1" +#~ msgstr "GroupBox1" + +#~ msgid "[ ±0.15V]" +#~ msgstr "[ ±0.15V]" + +#~ msgid "[3,2 - 4,4V]" +#~ msgstr "[3,2 - 4,4V]" + +#~ msgid "[0 - V]" +#~ msgstr "[0 - V]" + +#~ msgid "Bar" +#~ msgstr "Bar" + +#~ msgid "Ausgangsspannung:" +#~ msgstr "Voltag out:" + +#~ msgid "Strom:" +#~ msgstr "Current:" + +#~ msgid "Temperatur:" +#~ msgstr "Temperature:" + +#~ msgid "Stand:" +#~ msgstr "Time:" + +#~ msgid "Kanal:" +#~ msgstr "Channel:" + +#~ msgid "Ladung:" +#~ msgstr "Charge:" + +#~ msgid "Eingangsspannung:" +#~ msgstr "Voltage in:" + +#~ msgid "Zyklus:" +#~ msgstr "Cycle:" + +#~ msgid "Max. Zellendifferenz:" +#~ msgstr "Max. Cell difference:" + +#~ msgid "Ultramat Duo Plus 50" +#~ msgstr "Ultramat Duo Plus 50" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6444" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6444" + +#~ msgid "Ultramat Duo Plus 45" +#~ msgstr "Ultramat Duo Plus 45" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6475" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6475" + +#~ msgid "Ultramat Duo Plus 40" +#~ msgstr "Ultramat Duo Plus 40" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6443" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6443" + +#~ msgid "Programmierbar" +#~ msgstr "Programmable" + +#~ msgid "Live-Anzeige" +#~ msgstr "Live-Display" + +#~ msgid "Ultra Trio Plus 14" +#~ msgstr "Ultra Trio Plus 14" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6466" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6466" + +#~ msgid "Ultramat 16S" +#~ msgstr "Ultramat 16S" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6468" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6468" + +#~ msgid "Ultramat 18" +#~ msgstr "Ultramat 18" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6470" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6470" + +#~ msgid "http://www.graupner.de" +#~ msgstr "http://www.graupner.com" + +#~ msgid "Ultramat Duo Plus 60" +#~ msgstr "Ultramat Duo Plus 60" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6478" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6478" + +#~ msgid "Live" +#~ msgstr "Live" + +#~ msgid "Einzel-Akku" +#~ msgstr "Single battery" + +#~ msgid "Über" +#~ msgstr "About" + +#~ msgid "Akkuhistorie" +#~ msgstr "Battery history" + +#~ msgid "Graphen" +#~ msgstr "Graphs" + +#~ msgid "Komplett" +#~ msgstr "All batterys" + +#~ msgid "Ladegerät" +#~ msgstr "Charger" + +#~ msgid "COM-Port: COM1" +#~ msgstr "COM-Port: COM1" + +#~ msgid "XXX" +#~ msgstr "XXX" + +#~ msgid "SingleAkku" +#~ msgstr "SingleAkku" + +#~ msgid "Akku Lesen" +#~ msgstr "Read battery" + +#~ msgid "Datei" +#~ msgstr "File" + +#~ msgid "Akku Schreiben" +#~ msgstr "Write battery" + +#~ msgid "Akku Laden" +#~ msgstr "Load battery" + +#~ msgid "Akku Speichern" +#~ msgstr "Save battery" + +#~ msgid "Neuer Akku" +#~ msgstr "New battery" + +#~ msgid "TSAkkuList" +#~ msgstr "TSAkkuList" + +#~ msgid "Liste Laden" +#~ msgstr "Load list" + +#~ msgid "Liste Speichern" +#~ msgstr "Save list" + +#~ msgid "#" +#~ msgstr "#" + +#~ msgid "Org #" +#~ msgstr "Org #" + +#~ msgid "Typ" +#~ msgstr "Type" + +#~ msgid "Zellen" +#~ msgstr "Cells" + +#~ msgid "Kapazität" +#~ msgstr "Capacity" + +#~ msgid "Daten Laden" +#~ msgstr "Load data" + +#~ msgid "Daten Speichern" +#~ msgstr "Save data" + +#~ msgid "tsViewData" +#~ msgstr "tsViewData" + +#~ msgid "TSGraphs" +#~ msgstr "TSGraphs" + +#~ msgid "Graphen lesen" +#~ msgstr "Read graphs" + +#~ msgid "tsLiveData" +#~ msgstr "tsLiveData" + +#~ msgid "name (*)|fil.*|a|b" +#~ msgstr "name (*)|fil.*|a|b" + +#~ msgid "Bearbeiten" +#~ msgstr "Edit" + +#~ msgid "Kopieren nach" +#~ msgstr "Copy to" + +#~ msgid "fProgress" +#~ msgstr "fProgress" + +#~ msgid "lProgress" +#~ msgstr "lProgress" + +#~ msgid "lRemaining" +#~ msgstr "lRemaining" + +#~ msgid "lBytes" +#~ msgstr "lBytes" + +#~ msgid "Speicher bearbeiten" +#~ msgstr "Edit memory" + +#~ msgid "Abbrechen" +#~ msgstr "Cancel" + +#~ msgid "Übernehmen" +#~ msgstr "Use" + +#~ msgid "Treiber Warnung" +#~ msgstr "Driver warning" + +#~ msgid "Sie haben keinen Treiber oder eine veraltete Version installiert!" +#~ msgstr "You have either no driver or an old driver installed!" + +#~ msgid "cURLUSBDrivers" +#~ msgstr "cURLUSBDrivers" + +#~ msgid "Einige ältere Treiber verursachen Abstürze (\"BlueScreens\"). " +#~ msgstr "Some older drivers are unstable and may produce \"bluescreens\"." + +#~ msgid "5.4.20.9" +#~ msgstr "5.4.20.9" + +#~ msgid "Internet-Adresse des Treiber vom Hersteller (anklicken):" +#~ msgstr "Internet-address of new driver from manufacturer (cllick):" + +#~ msgid "" +#~ "Die Treiber finden Sie in jedem Fall auf der Homepage zum Ladegerät von " +#~ "Graupner" +#~ msgstr "Die You can find that drivers on at the homepage of Graupner." + +#~ msgid "Neuste Version des Treibers (Stand: 19. Nov. 2010):" +#~ msgstr "Newest known driver version (Date:19. nov. 2010)" + +#~ msgid "Ihre Treiberversion:" +#~ msgstr "Your driver version:" + +#~ msgid "6.1" +#~ msgstr "6.1" + +#~ msgid "Nicht mehr überprüfen" +#~ msgstr "Do not check again" + +#~ msgid "Akku- und Zyklusdaten" +#~ msgstr "Battery- and cycledata" + +#~ msgid "Kapazität [mA]" +#~ msgstr "Capacity [mA]" + +#~ msgid "Widerstand [mOhm]" +#~ msgstr "Resistance [mOhm]" + +#~ msgid "Letzte Lade-Kapazität" +#~ msgstr "Last charge capacity" + +#~ msgid "Letzte Entlade-Kapazität" +#~ msgstr "Last discharge capacity" + +#~ msgid "Maximale Lade-Kapazität " +#~ msgstr "Max charge capacity" + +#~ msgid "Maximale Entlade-Kapazität " +#~ msgstr "Max discharge capacity" + +#~ msgid "Anzahl Ladungen" +#~ msgstr "Number of charges" + +#~ msgid "Miminaler Widerstand" +#~ msgstr "Min resistance" + +#~ msgid "Label1" +#~ msgstr "Label1" + +#~ msgid "Nr" +#~ msgstr "No" + +#~ msgid "Endzeit" +#~ msgstr "Finish time" + +#~ msgid "Ladekap." +#~ msgstr "Charge cap." + +#~ msgid "Entladekap." +#~ msgstr "Discharge cap." + +#~ msgid "Ladesp." +#~ msgstr "Charge volt." + +#~ msgid "Entladesp." +#~ msgstr "Disch. volt." + +#~ msgid "Ladewid." +#~ msgstr "Charge res." + +#~ msgid "Entladewid." +#~ msgstr "Disch. res." + +#~ msgid "Der benötigte Treiber konnte nicht gefunden werden." +#~ msgstr "The needed driver could not be found." + +#~ msgid "" +#~ "Wollen Sie direkt die Internetadresse öffnen, um den Treiber zu " +#~ "downloaden?" +#~ msgstr "Do you want to open the internet address, to download the driver?" + +#~ msgid " " +#~ msgstr " " + +#~ msgid "Kapazität:" +#~ msgstr "Capacity:" + +#~ msgid "K2" +#~ msgstr "Ch2" + +#~ msgid "Auswahl" +#~ msgstr "Select" + +#~ msgid "" +#~ "Dieses Programm dient einzig der DEMONSTRATION und ist NICHT für jegliche " +#~ "Veröffentlichung oder Weitergabe bestimmt!" +#~ msgstr "" +#~ "This program is intended ONLY for DEMONSTRATION and NOT for any " +#~ "distribution!" + +#~ msgid "Speicherplatz: %d" +#~ msgstr "Memory: %d" + +#~ msgid "Speichern" +#~ msgstr "Save" + +#~ msgid "Live-Speicher" +#~ msgstr "Live data" + +#~ msgid "Daten Lesen" +#~ msgstr "Read data" + +#~ msgid "Daten Schreiben" +#~ msgstr "Write data" + +#~ msgid "ComPort Library example" +#~ msgstr "ComPort Library example" + +#~ msgid "MS Sans Serif" +#~ msgstr "MS Sans Serif" + +#~ msgid "Open" +#~ msgstr "Open" + +#~ msgid "Send" +#~ msgstr "Send" + +#~ msgid "Send new line" +#~ msgstr "Send new line" + +#~ msgid "CTS" +#~ msgstr "CTS" + +#~ msgid "DSR" +#~ msgstr "DSR" + +#~ msgid "RLSD" +#~ msgstr "RLSD" + +#~ msgid "Ring" +#~ msgstr "Ring" + +#~ msgid "Tx" +#~ msgstr "Tx" + +#~ msgid "Rx" +#~ msgstr "Rx" + +#~ msgid "Load" +#~ msgstr "Load" + +#~ msgid "Nach MS-Excel exportieren" +#~ msgstr "Export to MS-Excel" + +#~ msgid "Mini Terminal" +#~ msgstr "Mini Terminal" + +#~ msgid "Terminal ready" +#~ msgstr "Terminal ready" + +#~ msgid "Carrier detected" +#~ msgstr "Carrier detected" + +#~ msgid "Connect" +#~ msgstr "Connect" + +#~ msgid "Serial Port" +#~ msgstr "Serial Port" + +#~ msgid "Terminal" +#~ msgstr "Terminal" + +#~ msgid "Font" +#~ msgstr "Font" + +#~ msgid "System" +#~ msgstr "System" + +#~ msgid "Non-Overlapped API Test" +#~ msgstr "Non-Overlapped API Test" + +#~ msgid "COM3" +#~ msgstr "COM3" + +#~ msgid "Copy" +#~ msgstr "Copy" + +#~ msgid "Paste" +#~ msgstr "Paste" + +#~ msgid "TComPort" +#~ msgstr "TComPort" + +#~ msgid "MS Serif" +#~ msgstr "MS Serif" + +#~ msgid "version" +#~ msgstr "version" + +#~ msgid "by Dejan Crnila, Lars Dybdahl, and Warren Postma" +#~ msgstr "by Dejan Crnila, Lars Dybdahl, and Warren Postma" + +#~ msgid "See http://comport.sf.net/ for more info" +#~ msgstr "See http://comport.sf.net/ for more info" + +#~ msgid "Setup" +#~ msgstr "Setup" + +#~ msgid "Cancel" +#~ msgstr "Cancel" + +#~ msgid " Settings " +#~ msgstr " Settings " + +#~ msgid "Baud rate" +#~ msgstr "Baud rate" + +#~ msgid "Data bits" +#~ msgstr "Data bits" + +#~ msgid "Stop bits" +#~ msgstr "Stop bits" + +#~ msgid "Parity" +#~ msgstr "Parity" + +#~ msgid "Flow control" +#~ msgstr "Flow control" + +#~ msgid "Custom" +#~ msgstr "Custom" + +#~ msgid "None" +#~ msgstr "None" + +#~ msgid "Hardware" +#~ msgstr "Hardware" + +#~ msgid "COM11" +#~ msgstr "COM11" + +#~ msgid " ASCII Settings " +#~ msgstr " ASCII Settings " + +#~ msgid "Echo typed charachters locally" +#~ msgstr "Echo typed charachters locally" + +#~ msgid "Send line feeds with carriage return" +#~ msgstr "Send line feeds with carriage return" + +#~ msgid "Wrap lines that exceed terminal width" +#~ msgstr "Wrap lines that exceed terminal width" + +#~ msgid "Force incoming data to 7 bit ASCII" +#~ msgstr "Force incoming data to 7 bit ASCII" + +#~ msgid "Append line feeds to incoming carriage return" +#~ msgstr "Append line feeds to incoming carriage return" + +#~ msgid " Terminal Settings " +#~ msgstr " Terminal Settings " + +#~ msgid "Caret" +#~ msgstr "Caret" + +#~ msgid "Columns" +#~ msgstr "Columns" + +#~ msgid "Rows" +#~ msgstr "Rows" + +#~ msgid "Cursor keys" +#~ msgstr "Cursor keys" + +#~ msgid "Block" +#~ msgstr "Block" + +#~ msgid "Underline" +#~ msgstr "Underline" + +#~ msgid "ANSI/VT100" +#~ msgstr "ANSI/VT100" + +#~ msgid "VT52" +#~ msgstr "VT52" + +#~ msgid "Act as terminal keys" +#~ msgstr "Act as terminal keys" + +#~ msgid "Act as windows keys" +#~ msgstr "Act as windows keys" + +#~ msgid "" +#~ "Unit Test for TComPort Component and CPORTU modifications (W. Postma)" +#~ msgstr "" +#~ "Unit Test for TComPort Component and CPORTU modifications (W. Postma)" + +#~ msgid "Insert loopback plug into your serial port," +#~ msgstr "Insert loopback plug into your serial port," + +#~ msgid "select the serial port from the combo-box," +#~ msgstr "select the serial port from the combo-box," + +#~ msgid "and then click Run Test." +#~ msgstr "and then click Run Test." + +#~ msgid "Com Port" +#~ msgstr "Com Port" + +#~ msgid "Result: Not Run Yet." +#~ msgstr "Result: Not Run Yet." + +#~ msgid "Run Test" +#~ msgstr "Run Test" diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/delphi2006_de_en.mo b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/delphi2006_de_en.mo new file mode 100755 index 0000000000..56f80e6008 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/delphi2006_de_en.mo differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/xampp_control.mo b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/xampp_control.mo new file mode 100755 index 0000000000..b7b0c3fa57 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/xampp_control.mo differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/xampp_control.po b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/xampp_control.po new file mode 100755 index 0000000000..d7905a3a72 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/locale/en/LC_MESSAGES/xampp_control.po @@ -0,0 +1,1831 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: UDP Manager\n" +"POT-Creation-Date: 2011-05-10 10:34+0100\n" +"PO-Revision-Date: 2011-05-10 10:34+0100\n" +"Last-Translator: Steffen Strüber \n" +"Language-Team: Strueber-IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: dxgettext 1.2.1\n" +"X-Poedit-Language: English\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: iso-8859-1\n" +"X-Poedit-Basepath: C:\\Delphi\\xampp_control3\n" +"X-Poedit-SearchPath-0: .\n" + +#. fConfig..Caption +#. fMain..bConfig..Caption +#. fMain..gbModules..bApacheConfig..Caption +#. fMain..gbModules..bMySQLConfig..Caption +#. fMain..gbModules..bFileZillaConfig..Caption +#. fMain..gbModules..bMercuryConfig..Caption +#: uConfig.dfm:4 +#: uMain.dfm:1032 +#: uMain.dfm:1307 +#: uMain.dfm:1329 +#: uMain.dfm:1351 +#: uMain.dfm:1373 +#, fuzzy +msgid "Config" +msgstr "tsLoaderConfig" + +#. fConfig..Font.Name +#. fConfig..GroupBox1..Label3..Font.Name +#. fHelp..Font.Name +#. fHelp..lAbout..Font.Name +#. fLanguage..Font.Name +#. fNetstat..Font.Name +#. fNetstat..Panel1..Font.Name +#. fNetstat..Panel2..Font.Name +#. fNetstat..Panel3..Font.Name +#: uConfig.dfm:11 +#: uConfig.dfm:248 +#: uHelp.dfm:11 +#: uHelp.dfm:29 +#: uLanguage.dfm:13 +#: uNetstat.dfm:12 +#: uNetstat.dfm:90 +#: uNetstat.dfm:106 +#: uNetstat.dfm:122 +msgid "Tahoma" +msgstr "Tahoma" + +#. fConfig..Label1..Caption +#: uConfig.dfm:26 +msgid "Editor:" +msgstr "" + +#. fConfig..Label2..Caption +#: uConfig.dfm:33 +msgid "Browser (empty = system default)" +msgstr "" + +#. fConfig..bSave..Caption +#: uConfig.dfm:103 +msgid "Save" +msgstr "" + +#. fConfig..bAbort..Caption +#: uConfig.dfm:135 +#, fuzzy +msgid "Abort" +msgstr "About" + +#. fConfig..cbDebug..Caption +#: uConfig.dfm:210 +msgid "Show debug stuff" +msgstr "" + +#. fConfig..cbDebugDetails....Items.Strings +#: uConfig.dfm:223 +msgid "Details" +msgstr "Details" + +#. fConfig..cbDebugDetails....Items.Strings +#: uConfig.dfm:224 +msgid "Very many details" +msgstr "" + +#. fConfig..GroupBox1..Caption +#: uConfig.dfm:232 +msgid " Autostart of modules " +msgstr "" + +#. fConfig..GroupBox1..Label3..Caption +#: uConfig.dfm:244 +msgid "Selected modules will be started on next launch of this program." +msgstr "" + +#. fConfig..GroupBox1..cbASApache..Caption +#. fMain..gbModules..pApacheStatus..Caption +#: uConfig.dfm:258 +#: uMain.dfm:1298 +#, fuzzy +msgid "Apache" +msgstr "Language" + +#. fConfig..GroupBox1..cbASMySQL..Caption +#. fMain..gbModules..pMySQLStatus..Caption +#: uConfig.dfm:266 +#: uMain.dfm:1385 +msgid "MySQL" +msgstr "" + +#. fConfig..GroupBox1..cbASFileZilla..Caption +#. fMain..gbModules..pFileZillaStatus..Caption +#: uConfig.dfm:274 +#: uMain.dfm:1395 +msgid "FileZilla" +msgstr "" + +#. fConfig..GroupBox1..cbASMercury..Caption +#. fMain..gbModules..pMercuryStatus..Caption +#: uConfig.dfm:282 +#: uMain.dfm:1405 +msgid "Mercury" +msgstr "" + +#. fConfig..cbCheckDefaultPorts..Caption +#: uConfig.dfm:292 +msgid "Check default ports on startup" +msgstr "" + +#. fConfig..BitBtn1..Caption +#: uConfig.dfm:301 +msgid "Change language" +msgstr "" + +#. fConfig..OpenDialog1..DefaultExt +#: uConfig.dfm:308 +msgid "exe" +msgstr "" + +#. fConfig..OpenDialog1..Filter +#: uConfig.dfm:309 +msgid "Executables (*.exe)|*.exe|All files (*.*)|*.*" +msgstr "" + +#. fHelp..Caption +#. fMain..bHelp..Caption +#: uHelp.dfm:4 +#: uMain.dfm:1457 +msgid "Help" +msgstr "" + +#. fHelp..lAbout..Caption +#: uHelp.dfm:25 +msgid "programmed by Steffen Strueber" +msgstr "" + +#. fHelp..Label1..Caption +#: uHelp.dfm:38 +msgid "Uhm, did this help? :-)" +msgstr "" + +#. fHelp..BitBtn1..Caption +#: uHelp.dfm:46 +msgid "Close" +msgstr "" + +#. fLanguage..Caption +#: uLanguage.dfm:5 +msgid "Konfiguration" +msgstr "Configuration" + +#. fLanguage..bOkay..Caption +#: uLanguage.dfm:167 +msgid "OK" +msgstr "OK" + +#. fMain..Caption +#: uMain.dfm:4 +msgid "XAMPP Control Panel Application v3.0" +msgstr "" + +#. fMain..Font.Name +#. fMain..lHeader..Font.Name +#. fMain..reLog..Font.Name +#. fMain..gbModules..lPIDs..Font.Name +#. fMain..gbModules..lPorts..Font.Name +#. fMain..gbModules..Label1..Font.Name +#. fMain..gbModules..Label2..Font.Name +#. fMain..gbModules..Label3..Font.Name +#: uMain.dfm:11 +#: uMain.dfm:1006 +#: uMain.dfm:1019 +#: uMain.dfm:1066 +#: uMain.dfm:1080 +#: uMain.dfm:1170 +#: uMain.dfm:1185 +#: uMain.dfm:1199 +msgid "Verdana" +msgstr "" + +#. fMain..lHeader..Caption +#: uMain.dfm:1002 +msgid "XAMPP Control Panel v3.0.1" +msgstr "" + +#. fMain..bSCM..Caption +#: uMain.dfm:1043 +msgid "Win-Services" +msgstr "" + +#. fMain..gbModules..Caption +#: uMain.dfm:1054 +#, fuzzy +msgid " Modules " +msgstr "lModus" + +#. fMain..gbModules..lPIDs..Caption +#: uMain.dfm:1062 +msgid "PID(s)" +msgstr "" + +#. fMain..gbModules..lPorts..Caption +#: uMain.dfm:1076 +#, fuzzy +msgid "Port(s)" +msgstr "Port" + +#. fMain..gbModules..Label1..Caption +#: uMain.dfm:1166 +msgid "SCV" +msgstr "" + +#. fMain..gbModules..Label2..Caption +#: uMain.dfm:1181 +#, fuzzy +msgid "Module" +msgstr "lModus" + +#. fMain..gbModules..Label3..Caption +#: uMain.dfm:1195 +msgid "Action" +msgstr "" + +#. fMain..gbModules..bApacheAction..Caption +#. fMain..gbModules..bMySQLAction..Caption +#. fMain..gbModules..bFileZillaAction..Caption +#. fMain..gbModules..bMercuryAction..Caption +#: uMain.dfm:1208 +#: uMain.dfm:1230 +#: uMain.dfm:1252 +#: uMain.dfm:1275 +#, fuzzy +msgid "Start" +msgstr "lStatus" + +#. fMain..gbModules..bApacheAdmin..Caption +#. fMain..gbModules..bMySQLAdmin..Caption +#. fMain..gbModules..bFileZillaAdmin..Caption +#. fMain..gbModules..bMercuryAdmin..Caption +#: uMain.dfm:1219 +#: uMain.dfm:1241 +#: uMain.dfm:1263 +#: uMain.dfm:1286 +#, fuzzy +msgid "Admin" +msgstr "min" + +#. fMain..gbModules..bApacheLogs..Caption +#. fMain..gbModules..bMySQLLogs..Caption +#. fMain..gbModules..bFileZillaLogs..Caption +#: uMain.dfm:1318 +#: uMain.dfm:1340 +#: uMain.dfm:1362 +msgid "Logs" +msgstr "" + +#. fMain..bQuit..Caption +#: uMain.dfm:1446 +msgid "Quit" +msgstr "" + +#. fMain..bExplorer..Caption +#: uMain.dfm:1468 +#, fuzzy +msgid "Explorer" +msgstr "Exporting" + +#. fMain..bNetstat..Caption +#: uMain.dfm:1479 +msgid "Netstat" +msgstr "" + +#. fMain..puSystray..miShowHide..Caption +#: uMain.dfm:2482 +#, fuzzy +msgid "Anzeigen" +msgstr "Display" + +#. fMain..puSystray..N1..Caption +#. fMain..puApacheConfig..Mehr1..N2..Caption +#: uMain.dfm:2487 +#: uMain.dfm:2534 +msgid "-" +msgstr "-" + +#. fMain..puSystray..miTerminate..Caption +#: uMain.dfm:2490 +#, fuzzy +msgid "Beenden" +msgstr "Terminating..." + +#. fMain..puApacheConfig..miApacheConfigApache..Caption +#: uMain.dfm:2499 +msgid "Apache (httpd.conf)" +msgstr "" + +#. fMain..puApacheConfig..miApacheConfigPHP..Caption +#: uMain.dfm:2503 +msgid "PHP (php.ini)" +msgstr "" + +#. fMain..puApacheConfig..Mehr1..Caption +#: uMain.dfm:2507 +#, fuzzy +msgid "More" +msgstr "Store" + +#. fMain..puApacheConfig..Mehr1..httpdvhostsconf1..Caption +#: uMain.dfm:2510 +msgid "apache\\conf\\extra\\httpd-vhosts.conf" +msgstr "" + +#. fMain..puApacheConfig..Mehr1..httpdxamppconf1..Caption +#: uMain.dfm:2515 +msgid "apache\\conf\\extra\\httpd-xampp.conf" +msgstr "" + +#. fMain..puApacheConfig..Mehr1..httpdsslconf1..Caption +#: uMain.dfm:2520 +msgid "apache\\conf\\extra\\httpd-ssl.conf" +msgstr "" + +#. fMain..puApacheConfig..Mehr1..httpddefaultconf1..Caption +#: uMain.dfm:2525 +msgid "apache\\conf\\extra\\httpd-default.conf" +msgstr "" + +#. fMain..puApacheConfig..Mehr1..httpddavconf1..Caption +#: uMain.dfm:2530 +msgid "apache\\conf\\extra\\httpd-dav.conf" +msgstr "" + +#. fMain..puApacheConfig..Mehr1..Browse1..Caption +#: uMain.dfm:2538 +msgid "" +msgstr "" + +#. fMain..puApacheLogs..miApacheLogsAccess..Caption +#: uMain.dfm:2547 +msgid "access.log" +msgstr "" + +#. fMain..puApacheLogs..miApacheLogsError..Caption +#: uMain.dfm:2551 +msgid "error.log" +msgstr "" + +#. fNetstat..Caption +#: uNetstat.dfm:5 +msgid "Netstat - TCP Listening sockets" +msgstr "" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:33 +msgid "Address" +msgstr "" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:38 +msgid "Port" +msgstr "Port" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:42 +msgid "PID" +msgstr "" + +#. fNetstat..ListView1......Caption +#: uNetstat.dfm:45 +msgid "Name" +msgstr "Name" + +#. fNetstat..BitBtn1..Caption +#: uNetstat.dfm:65 +msgid "Refresh" +msgstr "" + +#. fNetstat..Panel1..Caption +#: uNetstat.dfm:85 +msgid "Active socket" +msgstr "" + +#. fNetstat..Panel2..Caption +#: uNetstat.dfm:101 +msgid "Old socket" +msgstr "" + +#. fNetstat..Panel3..Caption +#: uNetstat.dfm:117 +msgid "New socket" +msgstr "" + +#~ msgid "Graupner GmbH && Co. KG" +#~ msgstr "Graupner GmbH && Co. KG" + +#~ msgid "Henriettenstraße 94-96" +#~ msgstr "Henriettenstraße 94-96" + +#~ msgid "73230 Kirchheim/Teck" +#~ msgstr "73230 Kirchheim/Teck" + +#~ msgid "Vertretungsberechtigte Geschäftsführer" +#~ msgstr "Authorised Representative Managing Directors" + +#~ msgid "Hans Graupner, Stefan Graupner" +#~ msgstr "Hans Graupner, Stefan Graupner" + +#~ msgid "Telefon/Fax/E-Mail-Adresse" +#~ msgstr "Phone/Fax/E-mail address" + +#~ msgid "Telefon: 07021 722-0" +#~ msgstr "Phone: +49 7021 722-0" + +#~ msgid "Fax: 07021 722-200" +#~ msgstr "Fax: +49 7021 722-200" + +#~ msgid "Email: info@graupner.de" +#~ msgstr "Email: info@graupner.de" + +#~ msgid "Registergericht:" +#~ msgstr "Court of registration:" + +#~ msgid "Amtsgericht Stuttgart HRA 230523" +#~ msgstr "Stuttgart District Court, Trade Register No. 230523" + +#~ msgid "Umsatzsteueridentifikationsnummer" +#~ msgstr "VAT identification number" + +#~ msgid "DE 145929079" +#~ msgstr "DE 145929079" + +#~ msgid "Akku Zellen (x %.1f V)" +#~ msgstr "Battery cells (x %.1f V)" + +#~ msgid "neu" +#~ msgstr "new" + +#~ msgid "Speicherplatz: %s" +#~ msgstr "Battery number: %s" + +#~ msgid "==> %.1f V" +#~ msgstr "==> %.1f V" + +#~ msgid "Standardwert (für Akku-Typ=%s)" +#~ msgstr "Default (for Battery type=%s)" + +#~ msgid "Serieller Port konnte nicht geöffnet werden." +#~ msgstr "Serial port could not be opened" + +#~ msgid "Leerlauf" +#~ msgstr "Idle" + +#~ msgid "Laden" +#~ msgstr "Charge" + +#~ msgid "Entladen" +#~ msgstr "Discharge" + +#~ msgid "Wartezeit" +#~ msgstr "Delay" + +#~ msgid "Hitze-Schutz" +#~ msgstr "Pause time (Charger too hot)" + +#~ msgid "Vorgang abgeschlossen" +#~ msgstr "Operation completed" + +#~ msgid "Fehler" +#~ msgstr "Error" + +#~ msgid "Balancen" +#~ msgstr "Balance" + +#~ msgid "Heizung/Netzteil" +#~ msgstr "Tyre Heater" + +#~ msgid "DC-Motor" +#~ msgstr "Motor mode" + +#~ msgid "Lagermodus: Laden" +#~ msgstr "Storemode: Charge" + +#~ msgid "Lagermodus: Entladen" +#~ msgstr "Storemode: Discharge" + +#~ msgid "Schreibe" +#~ msgstr "Writing" + +#~ msgid "Lese" +#~ msgstr "Reading" + +#~ msgid "" +#~ "Achtung, die Firmware Ihres Ladergeräts ist in Bezug auf die PC-" +#~ "kommunikation fehlerhaft!" +#~ msgstr "" +#~ "Attention, the firmware of your charger has some errors related to " +#~ "communication to the PC!" + +#~ msgid "" +#~ "Um dieses Programm mit Ihrem Ladegrät nutzen zu können, müssen Sie die " +#~ "Firmware updaten." +#~ msgstr "" +#~ "To use this program with your charger, you have to upgrade the chargers " +#~ "firmware ." + +#~ msgid "" +#~ "Besuchen Sie dazu die Homepage von Graupner bzw. klicken Sie im Programm " +#~ "auf das Ladegerät UDP 40, um automatisch die entsprechende Seite zu laden." +#~ msgstr "" +#~ "Visit the homepage of Graupner or click on the icon of UDP 40 in this " +#~ "program." + +#~ msgid "" +#~ "Das Programm zum Updaten der Firmware finden Sie evtl. nur auf der Seite " +#~ "des UDP 50!" +#~ msgstr "" +#~ "The program to update the firmware can be found on the page of the UDP 50." + +#~ msgid "K" +#~ msgstr "Ch" + +#~ msgid " Linke Seite " +#~ msgstr " Left side " + +#~ msgid "Das Programm muss neu gestartet werden. Weiter?" +#~ msgstr "The program has to be restarted. Proceed?" + +#~ msgid "--- Automatisch erkannte COM-Ports ---" +#~ msgstr "--- Automatic detected COM-Ports ---" + +#~ msgid "--- Manuelle Eingabe des COM-Ports ---" +#~ msgstr "--- Manual selection of COM-Ports ---" + +#~ msgid "Fullscreen" +#~ msgstr "Fullscreen" + +#~ msgid "links" +#~ msgstr "left" + +#~ msgid "Spannung" +#~ msgstr "Voltage" + +#~ msgid "rechts" +#~ msgstr "right" + +#~ msgid "Strom" +#~ msgstr "Current" + +#~ msgid "Temperatur" +#~ msgstr "Temperature" + +#~ msgid "Werte Daten aus..." +#~ msgstr "Interpreting data..." + +#~ msgid "Keine Daten geladen!" +#~ msgstr "No data loaded!" + +#~ msgid "Exportiert am %s" +#~ msgstr "Exported on %s" + +#~ msgid "Messpunkte - Intervall = %d Sekunden" +#~ msgstr "measuring interval = %d seconds" + +#~ msgid "Spannung [V]" +#~ msgstr "Voltage [V]" + +#~ msgid "Strom [mA]" +#~ msgstr "Current [mA]" + +#~ msgid "Temperatur [C°]" +#~ msgstr "Temperature [°C]" + +#~ msgid "Links" +#~ msgstr "Left" + +#~ msgid "Rechts" +#~ msgstr "Right" + +#~ msgid "Intervall" +#~ msgstr "Interval" + +#~ msgid "Sek" +#~ msgstr "Sec" + +#~ msgid "Vollbild" +#~ msgstr "Fullscreen" + +#~ msgid "Schließen" +#~ msgstr "Close" + +#~ msgid "Das Dateiformat ist nicht mit dieser Version kompatibel!" +#~ msgstr "The file format is not compatible with this version!" + +#~ msgid "Lade Daten..." +#~ msgstr "Loading data..." + +#~ msgid "Schreibe Daten..." +#~ msgstr "Writing data..." + +#~ msgid "Daten gespeichert!" +#~ msgstr "Data saved!" + +#~ msgid "Aktuell" +#~ msgstr "Current" + +#~ msgid "Zellenspannung" +#~ msgstr "Cell voltage" + +#~ msgid "Gesamtspannung" +#~ msgstr "Total voltage" + +#~ msgid "Ladung" +#~ msgstr "Kapazität" + +#~ msgid "K%d: " +#~ msgstr "Ch%d:" + +#~ msgid "Zelle %d" +#~ msgstr "Cell %d" + +#~ msgid "Zyklus" +#~ msgstr "Cycle" + +#~ msgid "Uhrzeit" +#~ msgstr "Time" + +#~ msgid "Kanal" +#~ msgstr "Channel" + +#~ msgid "Zelle" +#~ msgstr "Cell" + +#~ msgid "UDP Manager v%s - Copyright by Graupner GmbH 2009-2010" +#~ msgstr "UDP Manager v%s - Copyright by Graupner GmbH 2009-2010" + +#~ msgid "UDP Manager v%s" +#~ msgstr "UDP Manager v%s" + +#~ msgid "Einzel-Speicher" +#~ msgstr "Single-Memory" + +#~ msgid "Multi-Speicher" +#~ msgstr "Multi-Memory" + +#~ msgid "Live-Daten" +#~ msgstr "Live-Data" + +#~ msgid "Alle Dateien" +#~ msgstr "All files" + +#~ msgid "Sie müssen eine gültige Zahl als Speichernummer angeben!" +#~ msgstr "You must enter a valid memory number!" + +#~ msgid "Erlaubte Speichernummer: 1 bis %d" +#~ msgstr "Allowed memory number: 1 to %d" + +#~ msgid "Lade Speicher..." +#~ msgstr "Loading memory..." + +#~ msgid "Starte PC-Mode" +#~ msgstr "Starting PC-Mode" + +#~ msgid "Verlasse PC-Mode" +#~ msgstr "Leaving PC-Mode" + +#~ msgid "Keine vollständigen Daten geladen!" +#~ msgstr "No complete data loaded!" + +#~ msgid "" +#~ "Quellspeicher-Nummer und Zielspeicher-Nummer sind unterschiedlich. Weiter?" +#~ msgstr "Source-number and destination-number are not equal. Proceed?" + +#~ msgid "%d/%d (%.0f %%)" +#~ msgstr "%d/%d (%.0f %%)" + +#~ msgid "Fehler!" +#~ msgstr "Error!" + +#~ msgid "Sie müssen alle Daten eingeben!" +#~ msgstr "You have to fill out all data fields!" + +#~ msgid "Gerät konnte nicht erkannt werden!" +#~ msgstr "Charger could not be detected!" + +#~ msgid "Gerät nicht bereit. Beenden Sie alle Ladevorgänge etc.!" +#~ msgstr "Charger not ready. Finish all operations like charging etc!" + +#~ msgid "Fehler: %s" +#~ msgstr "Error: %s" + +#~ msgid "unbekannt" +#~ msgstr "unknown" + +#~ msgid "COM-Port geöffnet" +#~ msgstr "COM-Port opened" + +#~ msgid "COM-Port geschlossen" +#~ msgstr "COM-Port closed" + +#~ msgid "Unbekanntes Gerät" +#~ msgstr "Unknown device" + +#~ msgid "Initialisiere" +#~ msgstr "Initializing" + +#~ msgid "Wähle Sheet aus" +#~ msgstr "Selecting sheet" + +#~ msgid "Bereite Format vor" +#~ msgstr "Preparing format" + +#~ msgid "%d / %d, %d%% fertig" +#~ msgstr "%d / %d, %d%% done" + +#~ msgid "Excel konnte nicht gestartet werden!" +#~ msgstr "Excel could not be started!" + +#~ msgid "Eventuell ist kein Excel installiert?" +#~ msgstr "Maybe there is no MS-Excel installed?" + +#~ msgid "Starte Excel" +#~ msgstr "Starting Excel" + +#~ msgid "fAbout" +#~ msgstr "fAbout" + +#~ msgid "UDP Manager" +#~ msgstr "UDP Manager" + +#~ msgid "Arial" +#~ msgstr "Arial" + +#~ msgid "lLegalStuff" +#~ msgstr "lLegalStuff" + +#~ msgid "Entwickelt von" +#~ msgstr "Developed by" + +#~ msgid "Dipl.-Wirt.-Inf Steffen Strüber" +#~ msgstr "Dipl.-Wirt.-Inf Steffen Strüber" + +#~ msgid " Akkuinformation " +#~ msgstr " Battery information " + +#~ msgid "Akku Typ" +#~ msgstr "Battery type" + +#~ msgid "Akku Zellen" +#~ msgstr "Battery cells" + +#~ msgid "Akku Kapazität" +#~ msgstr "Battery capacity" + +#~ msgid "Inbetriebnahme" +#~ msgstr "Beginning of operation" + +#~ msgid "Akkuname (16 Zeichen)" +#~ msgstr "Battery name (16 charachters)" + +#~ msgid "Speicherplatz: 0" +#~ msgstr "Memory: 0" + +#~ msgid "mAh" +#~ msgstr "mAh" + +#~ msgid "=> 00.0V" +#~ msgstr "=> 00.0V" + +#~ msgid "Info: Anzahl Zyklen" +#~ msgstr "Info: Number of cycles" + +#~ msgid "NiCd" +#~ msgstr "NiCd" + +#~ msgid "NiMH" +#~ msgstr "NiMH" + +#~ msgid "LiIo" +#~ msgstr "LiIo" + +#~ msgid "LiPo" +#~ msgstr "LiPo" + +#~ msgid "LiFe" +#~ msgstr "LiFe" + +#~ msgid "Pb" +#~ msgstr "Pb" + +#~ msgid "eAkkuName" +#~ msgstr "eAkkuName" + +#~ msgid " Laden " +#~ msgstr " Load " + +#~ msgid "Ladestrom" +#~ msgstr "Charge current" + +#~ msgid "Peak Empfindlichkeit" +#~ msgstr "Peak sensibility" + +#~ msgid "Peak Verzögerung" +#~ msgstr "Peak delay" + +#~ msgid "Abschalttemperatur" +#~ msgstr "Cut-off temperature" + +#~ msgid "Erhaltungsstrom" +#~ msgstr "Trickle current" + +#~ msgid "Max Kapazität" +#~ msgstr "Max capacity" + +#~ msgid "%" +#~ msgstr "%" + +#~ msgid "Keine Spannnungsänderung" +#~ msgstr "No voltage change" + +#~ msgid "RePeak Pause" +#~ msgstr "RePeak pause" + +#~ msgid "RePeak Zyklen" +#~ msgstr "RePeak cylces" + +#~ msgid "Sicherheitstimer" +#~ msgstr "Security timer" + +#~ msgid "Ladespannung" +#~ msgstr "Charge voltage" + +#~ msgid "mA" +#~ msgstr "mA" + +#~ msgid "mV / Zelle" +#~ msgstr "mV / Zelle" + +#~ msgid "°C" +#~ msgstr "°C" + +#~ msgid "V / Zelle" +#~ msgstr "V / Zelle" + +#~ msgid "Lagerspannung" +#~ msgstr "Store voltage" + +#~ msgid "550 = AUS, 0=AUTO" +#~ msgstr "550 = OFF, 0=AUTO" + +#~ msgid "Aus" +#~ msgstr "Out" + +#~ msgid "An" +#~ msgstr "On" + +#~ msgid "905 = AUS" +#~ msgstr "905 = OFF" + +#~ msgid "155 = AUS" +#~ msgstr "155 = OFF" + +#~ msgid "Wert" +#~ msgstr "Value" + +#~ msgid "AUS" +#~ msgstr "OFF" + +#~ msgid "AUTO" +#~ msgstr "AUTO" + +#~ msgid " Zyklus " +#~ msgstr "Cycle" + +#~ msgid "Pause nach Entladen" +#~ msgstr "Pause after discharge" + +#~ msgid "Pause nach Laden" +#~ msgstr "Pause after charge" + +#~ msgid "Anzahl Zyklen" +#~ msgstr "Number of cycles" + +#~ msgid "Zyklus Reihenfolge" +#~ msgstr "Cycle order" + +#~ msgid "E:L->E" +#~ msgstr "D:C->D" + +#~ msgid "L->E" +#~ msgstr "C->D" + +#~ msgid "E->L" +#~ msgstr "D->C" + +#~ msgid " Entladen " +#~ msgstr " Discharge " + +#~ msgid "Entladestrom" +#~ msgstr "Disch. current" + +#~ msgid "Abschaltspannung" +#~ msgstr "Cut-off voltage" + +#~ msgid "Aschalttemperatur [°C]" +#~ msgstr "Cut-off temperature" + +#~ msgid "Max. Kapazität" +#~ msgstr "Max. capacity" + +#~ msgid "Balancerspannnung" +#~ msgstr "Balancer voltage" + +#~ msgid "V" +#~ msgstr "V" + +#~ msgid "105 = AUS" +#~ msgstr "105 = OFF" + +#~ msgid "Fertig-Melodie" +#~ msgstr "Finish melody" + +#~ msgid "LCD Kontrast" +#~ msgstr "LCD contrast" + +#~ msgid "Fertig-Melodie Dauer" +#~ msgstr "Finish melody duration" + +#~ msgid "Rollen" +#~ msgstr "Roll" + +#~ msgid "Letzte" +#~ msgstr "Last" + +#~ msgid "5 Sek" +#~ msgstr "5 Sec" + +#~ msgid "15 Sek" +#~ msgstr "15 Sec" + +#~ msgid "1 Min" +#~ msgstr "1 Min" + +#~ msgid " Allgemein " +#~ msgstr " Common " + +#~ msgid "Temperaturanzeige" +#~ msgstr "Temperature view" + +#~ msgid "Tastenton" +#~ msgstr "Key sound" + +#~ msgid "Netzteil / Batterie" +#~ msgstr "Powersupply / battery" + +#~ msgid "Zeitdarstellung" +#~ msgstr "Timeformat" + +#~ msgid "Aktuelles Datum" +#~ msgstr "Current date" + +#~ msgid "Aktuelle Zeit" +#~ msgstr "Current time" + +#~ msgid "A" +#~ msgstr "A" + +#~ msgid "Name Ladegerät (16 Zeichen)" +#~ msgstr "Name charging device" + +#~ msgid "°F" +#~ msgstr "°F" + +#~ msgid "Englisch" +#~ msgstr "English" + +#~ msgid "Deutsch" +#~ msgstr "German" + +#~ msgid "Französisch" +#~ msgstr "French" + +#~ msgid "Italienisch" +#~ msgstr "Italian" + +#~ msgid "12 H" +#~ msgstr "12 H" + +#~ msgid "24 H" +#~ msgstr "24 H" + +#~ msgid "eLoaderName" +#~ msgstr "eLoaderName" + +#~ msgid "PC-Zeit beim Schreiben benutzen" +#~ msgstr "Use Computer-time on write" + +#~ msgid "Netzteil2 / Batterie" +#~ msgstr "Powersupply2 / battery" + +#~ msgid "Lastverteilung" +#~ msgstr "Lload distribution" + +#~ msgid " Rechte Seite " +#~ msgstr " Right side " + +#~ msgid "COM-Port" +#~ msgstr "COM-Port" + +#~ msgid "Abbruch" +#~ msgstr "Cancel" + +#~ msgid "USB-Treiber" +#~ msgstr "USB-driver" + +#~ msgid "Debug Daten serielle Schnittstelle" +#~ msgstr "Debug data serial device" + +#~ msgid "Courier New" +#~ msgstr "Courier New" + +#~ msgid "mDebug" +#~ msgstr "mDebug" + +#~ msgid "In Zwischenspeicher kopieren" +#~ msgstr "Copy to clipboard" + +#~ msgid "Protokollierung pausieren" +#~ msgstr "Suspend logging" + +#~ msgid "Gerät neu starten" +#~ msgstr "Restart Device" + +#~ msgid "COM1" +#~ msgstr "COM1" + +#~ msgid "Restdauer" +#~ msgstr "Remaining time" + +#~ msgid "Berechne" +#~ msgstr "Calculating" + +#~ msgid "Restdauer: %d Sekunden" +#~ msgstr "Remaining time: %d seconds" + +#~ msgid "Buffer: %s/%d Bytes" +#~ msgstr "Buffer: %s/%d Bytes" + +#~ msgid "Dateiendung muss .BMP oder .JPG sein!" +#~ msgstr "Extension must be .BMP or .JPG!" + +#~ msgid "\"%s\" erfolgreich gespeichert!" +#~ msgstr "\"%s\" saves successfully!" + +#~ msgid "Daten" +#~ msgstr "Data" + +#~ msgid "Speicher %d/%d (%.0f %%)" +#~ msgstr "Memory %d/%d (%.0f %%)" + +#~ msgid "Schreibe Speicher..." +#~ msgstr "Writing memory..." + +#~ msgid "Fehler: " +#~ msgstr "Error: " + +#~ msgid "Länge der gelesenen Daten ungültig!" +#~ msgstr "Length mismatch of read data!" + +#~ msgid "" +#~ "Überprüfen Sie die COM-Port Einstellung und stellen Sie sicher, dass das " +#~ "Ladegerät zur Zeit nicht lädt!" +#~ msgstr "" +#~ "Recheck your COM-Port settings and ensure, that the charger is not busy!" + +#~ msgid "Interner Fehler: Noch zu parsende Daten vorhanden!" +#~ msgstr "Internal error: More data remaining!" + +#~ msgid "Namen" +#~ msgstr "Names" + +#~ msgid "Daten 1" +#~ msgstr "Daten 1" + +#~ msgid "Daten 2" +#~ msgstr "Daten 2" + +#~ msgid "Kein Treiber gefundenn!" +#~ msgstr "No driver found!" + +#~ msgid "Speicherplatz: %s (%d)" +#~ msgstr "Battery number: %s (%d)" + +#~ msgid "Speicherplatz" +#~ msgstr "Memory" + +#~ msgid "fFullscreen" +#~ msgstr "fFullscreen" + +#~ msgid "Temperatur [°C]" +#~ msgstr "Temperature [°C]" + +#~ msgid "Tabelle" +#~ msgstr "Table" + +#~ msgid "TChart" +#~ msgstr "TChart" + +#~ msgid "SeriesVoltageLeft" +#~ msgstr "SeriesVoltageLeft" + +#~ msgid "X" +#~ msgstr "X" + +#~ msgid "Y" +#~ msgstr "Y" + +#~ msgid "SeriesVoltageRight" +#~ msgstr "SeriesVoltageRight" + +#~ msgid "SeriesCurrentLeft" +#~ msgstr "SeriesCurrentLeft" + +#~ msgid "SeriesCurrentRight" +#~ msgstr "SeriesCurrentRight" + +#~ msgid "SeriesTempLeft" +#~ msgstr "SeriesTempLeft" + +#~ msgid "SeriesTempRight" +#~ msgstr "SeriesTempRight" + +#~ msgid "Excel-Export" +#~ msgstr "Excel-Export" + +#~ msgid "lDataPoints" +#~ msgstr "lDataPoints" + +#~ msgid "lFrom" +#~ msgstr "lFrom" + +#~ msgid "lTo" +#~ msgstr "lTo" + +#~ msgid "Legende" +#~ msgstr "Legend" + +#~ msgid "Datenpunkte" +#~ msgstr "Datapoints" + +#~ msgid "Alle Kanäle" +#~ msgstr "All channels" + +#~ msgid "Optionen" +#~ msgstr "Options" + +#~ msgid "Grafik speichern" +#~ msgstr "Save graphic" + +#~ msgid "Von:" +#~ msgstr "From:" + +#~ msgid "Bis:" +#~ msgstr "To:" + +#~ msgid "Status:" +#~ msgstr "Status:" + +#~ msgid "Modus:" +#~ msgstr "Mode:" + +#~ msgid "Punkte:" +#~ msgstr "Points:" + +#~ msgid "Neu" +#~ msgstr "New" + +#~ msgid "Daten laden" +#~ msgstr "Load data" + +#~ msgid "Daten speichern" +#~ msgstr "Save data" + +#~ msgid "Linienstärke" +#~ msgstr "Line width" + +#~ msgid "Diagramm-Titel" +#~ msgstr "Diagram title" + +#~ msgid "Durchschnitt" +#~ msgstr "Average" + +#~ msgid "Ladung [mAh]" +#~ msgstr "Capacity [mAh]" + +#~ msgid "Alle Graphen" +#~ msgstr "All Graphs" + +#~ msgid "Übersicht" +#~ msgstr "Overview" + +#~ msgid "PanelPCMain" +#~ msgstr "PanelPCMain" + +#~ msgid "Warte auf Daten..." +#~ msgstr "Waiting for data..." + +#~ msgid "#,##0.00" +#~ msgstr "#,##0.00" + +#~ msgid "#,##0.000" +#~ msgstr "#,##0.000" + +#~ msgid "Strom [mA] / Temperatur [°C]" +#~ msgstr "Current [mA] / Temperature [°C]" + +#~ msgid "#,##0.0" +#~ msgstr "#,##0.0" + +#~ msgid "Spannungen" +#~ msgstr "Voltages" + +#~ msgid "Strom/Temp" +#~ msgstr "Current/Temp" + +#~ msgid "jpg" +#~ msgstr "jpg" + +#~ msgid "" +#~ "Alle (*.bmp;*.jpg)|*.jpg;*.bmp|JPEG-Grafikdatei (*.jpg)|*.jpg|Bitmaps (*." +#~ "bmp)|*.bmp" +#~ msgstr "" +#~ "All (*.bmp;*.jpg)|*.jpg;*.bmp|JPEG-file (*.jpg)|*.jpg|Bitmaps (*.bmp)|*." +#~ "bmp" + +#~ msgid "Aktuell: 10 x 10" +#~ msgstr "Current: 10 x 10" + +#~ msgid "Größe: 800 x 600" +#~ msgstr "Size: 800 x 600" + +#~ msgid "Größe: 1024 x 768" +#~ msgstr "Size: 1024 x 768" + +#~ msgid "Größe: 1920 x 1200" +#~ msgstr "Size: 1920 x 1200" + +#~ msgid "Größe: 4000 x 2000" +#~ msgstr "Size: 4000 x 2000" + +#~ msgid "GroupBox1" +#~ msgstr "GroupBox1" + +#~ msgid "[ ±0.15V]" +#~ msgstr "[ ±0.15V]" + +#~ msgid "[3,2 - 4,4V]" +#~ msgstr "[3,2 - 4,4V]" + +#~ msgid "[0 - V]" +#~ msgstr "[0 - V]" + +#~ msgid "Bar" +#~ msgstr "Bar" + +#~ msgid "Ausgangsspannung:" +#~ msgstr "Voltag out:" + +#~ msgid "Strom:" +#~ msgstr "Current:" + +#~ msgid "Temperatur:" +#~ msgstr "Temperature:" + +#~ msgid "Stand:" +#~ msgstr "Time:" + +#~ msgid "Kanal:" +#~ msgstr "Channel:" + +#~ msgid "Ladung:" +#~ msgstr "Charge:" + +#~ msgid "Eingangsspannung:" +#~ msgstr "Voltage in:" + +#~ msgid "Zyklus:" +#~ msgstr "Cycle:" + +#~ msgid "Max. Zellendifferenz:" +#~ msgstr "Max. Cell difference:" + +#~ msgid "Ultramat Duo Plus 50" +#~ msgstr "Ultramat Duo Plus 50" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6444" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6444" + +#~ msgid "Ultramat Duo Plus 45" +#~ msgstr "Ultramat Duo Plus 45" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6475" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6475" + +#~ msgid "Ultramat Duo Plus 40" +#~ msgstr "Ultramat Duo Plus 40" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6443" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6443" + +#~ msgid "Programmierbar" +#~ msgstr "Programmable" + +#~ msgid "Live-Anzeige" +#~ msgstr "Live-Display" + +#~ msgid "Ultra Trio Plus 14" +#~ msgstr "Ultra Trio Plus 14" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6466" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6466" + +#~ msgid "Ultramat 16S" +#~ msgstr "Ultramat 16S" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6468" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6468" + +#~ msgid "Ultramat 18" +#~ msgstr "Ultramat 18" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6470" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6470" + +#~ msgid "http://www.graupner.de" +#~ msgstr "http://www.graupner.com" + +#~ msgid "Ultramat Duo Plus 60" +#~ msgstr "Ultramat Duo Plus 60" + +#~ msgid "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6478" +#~ msgstr "http://shop.graupner.de/webuerp/servlet/AI?ARTN=6478" + +#~ msgid "Live" +#~ msgstr "Live" + +#~ msgid "Einzel-Akku" +#~ msgstr "Single battery" + +#~ msgid "Über" +#~ msgstr "About" + +#~ msgid "Akkuhistorie" +#~ msgstr "Battery history" + +#~ msgid "Graphen" +#~ msgstr "Graphs" + +#~ msgid "Komplett" +#~ msgstr "All batterys" + +#~ msgid "Ladegerät" +#~ msgstr "Charger" + +#~ msgid "COM-Port: COM1" +#~ msgstr "COM-Port: COM1" + +#~ msgid "XXX" +#~ msgstr "XXX" + +#~ msgid "SingleAkku" +#~ msgstr "SingleAkku" + +#~ msgid "Speicher:" +#~ msgstr "Memory:" + +#~ msgid "Akku Lesen" +#~ msgstr "Read battery" + +#~ msgid "Datei" +#~ msgstr "File" + +#~ msgid "Akku Schreiben" +#~ msgstr "Write battery" + +#~ msgid "Akku Laden" +#~ msgstr "Load battery" + +#~ msgid "Akku Speichern" +#~ msgstr "Save battery" + +#~ msgid "Neuer Akku" +#~ msgstr "New battery" + +#~ msgid "TSAkkuList" +#~ msgstr "TSAkkuList" + +#~ msgid "Liste Laden" +#~ msgstr "Load list" + +#~ msgid "Liste Speichern" +#~ msgstr "Save list" + +#~ msgid "#" +#~ msgstr "#" + +#~ msgid "Org #" +#~ msgstr "Org #" + +#~ msgid "Typ" +#~ msgstr "Type" + +#~ msgid "Zellen" +#~ msgstr "Cells" + +#~ msgid "Kapazität" +#~ msgstr "Capacity" + +#~ msgid "Daten Laden" +#~ msgstr "Load data" + +#~ msgid "Daten Speichern" +#~ msgstr "Save data" + +#~ msgid "tsViewData" +#~ msgstr "tsViewData" + +#~ msgid "TSGraphs" +#~ msgstr "TSGraphs" + +#~ msgid "Graphen lesen" +#~ msgstr "Read graphs" + +#~ msgid "tsLiveData" +#~ msgstr "tsLiveData" + +#~ msgid "name (*)|fil.*|a|b" +#~ msgstr "name (*)|fil.*|a|b" + +#~ msgid "Bearbeiten" +#~ msgstr "Edit" + +#~ msgid "Kopieren nach" +#~ msgstr "Copy to" + +#~ msgid "fProgress" +#~ msgstr "fProgress" + +#~ msgid "lProgress" +#~ msgstr "lProgress" + +#~ msgid "lRemaining" +#~ msgstr "lRemaining" + +#~ msgid "lBytes" +#~ msgstr "lBytes" + +#~ msgid "Speicher bearbeiten" +#~ msgstr "Edit memory" + +#~ msgid "Abbrechen" +#~ msgstr "Cancel" + +#~ msgid "Übernehmen" +#~ msgstr "Use" + +#~ msgid "Treiber Warnung" +#~ msgstr "Driver warning" + +#~ msgid "Sie haben keinen Treiber oder eine veraltete Version installiert!" +#~ msgstr "You have either no driver or an old driver installed!" + +#~ msgid "cURLUSBDrivers" +#~ msgstr "cURLUSBDrivers" + +#~ msgid "Einige ältere Treiber verursachen Abstürze (\"BlueScreens\"). " +#~ msgstr "Some older drivers are unstable and may produce \"bluescreens\"." + +#~ msgid "5.4.20.9" +#~ msgstr "5.4.20.9" + +#~ msgid "Internet-Adresse des Treiber vom Hersteller (anklicken):" +#~ msgstr "Internet-address of new driver from manufacturer (cllick):" + +#~ msgid "" +#~ "Die Treiber finden Sie in jedem Fall auf der Homepage zum Ladegerät von " +#~ "Graupner" +#~ msgstr "Die You can find that drivers on at the homepage of Graupner." + +#~ msgid "Neuste Version des Treibers (Stand: 19. Nov. 2010):" +#~ msgstr "Newest known driver version (Date:19. nov. 2010)" + +#~ msgid "Ihre Treiberversion:" +#~ msgstr "Your driver version:" + +#~ msgid "6.1" +#~ msgstr "6.1" + +#~ msgid "Nicht mehr überprüfen" +#~ msgstr "Do not check again" + +#~ msgid "Akku- und Zyklusdaten" +#~ msgstr "Battery- and cycledata" + +#~ msgid "Kapazität [mA]" +#~ msgstr "Capacity [mA]" + +#~ msgid "Widerstand [mOhm]" +#~ msgstr "Resistance [mOhm]" + +#~ msgid "Letzte Lade-Kapazität" +#~ msgstr "Last charge capacity" + +#~ msgid "Letzte Entlade-Kapazität" +#~ msgstr "Last discharge capacity" + +#~ msgid "Maximale Lade-Kapazität " +#~ msgstr "Max charge capacity" + +#~ msgid "Maximale Entlade-Kapazität " +#~ msgstr "Max discharge capacity" + +#~ msgid "Anzahl Ladungen" +#~ msgstr "Number of charges" + +#~ msgid "Miminaler Widerstand" +#~ msgstr "Min resistance" + +#~ msgid "Label1" +#~ msgstr "Label1" + +#~ msgid "Nr" +#~ msgstr "No" + +#~ msgid "Endzeit" +#~ msgstr "Finish time" + +#~ msgid "Ladekap." +#~ msgstr "Charge cap." + +#~ msgid "Entladekap." +#~ msgstr "Discharge cap." + +#~ msgid "Ladesp." +#~ msgstr "Charge volt." + +#~ msgid "Entladesp." +#~ msgstr "Disch. volt." + +#~ msgid "Ladewid." +#~ msgstr "Charge res." + +#~ msgid "Entladewid." +#~ msgstr "Disch. res." + +#~ msgid "Der benötigte Treiber konnte nicht gefunden werden." +#~ msgstr "The needed driver could not be found." + +#~ msgid "" +#~ "Wollen Sie direkt die Internetadresse öffnen, um den Treiber zu " +#~ "downloaden?" +#~ msgstr "Do you want to open the internet address, to download the driver?" + +#~ msgid " " +#~ msgstr " " + +#~ msgid "Kapazität:" +#~ msgstr "Capacity:" + +#~ msgid "K2" +#~ msgstr "Ch2" + +#~ msgid "Auswahl" +#~ msgstr "Select" + +#~ msgid "" +#~ "Dieses Programm dient einzig der DEMONSTRATION und ist NICHT für jegliche " +#~ "Veröffentlichung oder Weitergabe bestimmt!" +#~ msgstr "" +#~ "This program is intended ONLY for DEMONSTRATION and NOT for any " +#~ "distribution!" + +#~ msgid "Speicherplatz: %d" +#~ msgstr "Memory: %d" + +#~ msgid "Speichern" +#~ msgstr "Save" + +#~ msgid "Live-Speicher" +#~ msgstr "Live data" + +#~ msgid "Daten Lesen" +#~ msgstr "Read data" + +#~ msgid "Daten Schreiben" +#~ msgstr "Write data" + +#~ msgid "ComPort Library example" +#~ msgstr "ComPort Library example" + +#~ msgid "MS Sans Serif" +#~ msgstr "MS Sans Serif" + +#~ msgid "Open" +#~ msgstr "Open" + +#~ msgid "Settings" +#~ msgstr "Settings" + +#~ msgid "Send" +#~ msgstr "Send" + +#~ msgid "Send new line" +#~ msgstr "Send new line" + +#~ msgid "CTS" +#~ msgstr "CTS" + +#~ msgid "DSR" +#~ msgstr "DSR" + +#~ msgid "RLSD" +#~ msgstr "RLSD" + +#~ msgid "Ring" +#~ msgstr "Ring" + +#~ msgid "Tx" +#~ msgstr "Tx" + +#~ msgid "Rx" +#~ msgstr "Rx" + +#~ msgid "Load" +#~ msgstr "Load" + +#~ msgid "Nach MS-Excel exportieren" +#~ msgstr "Export to MS-Excel" + +#~ msgid "Mini Terminal" +#~ msgstr "Mini Terminal" + +#~ msgid "Terminal ready" +#~ msgstr "Terminal ready" + +#~ msgid "Carrier detected" +#~ msgstr "Carrier detected" + +#~ msgid "Connect" +#~ msgstr "Connect" + +#~ msgid "Serial Port" +#~ msgstr "Serial Port" + +#~ msgid "Terminal" +#~ msgstr "Terminal" + +#~ msgid "Font" +#~ msgstr "Font" + +#~ msgid "System" +#~ msgstr "System" + +#~ msgid "Non-Overlapped API Test" +#~ msgstr "Non-Overlapped API Test" + +#~ msgid "COM3" +#~ msgstr "COM3" + +#~ msgid "Copy" +#~ msgstr "Copy" + +#~ msgid "Paste" +#~ msgstr "Paste" + +#~ msgid "TComPort" +#~ msgstr "TComPort" + +#~ msgid "MS Serif" +#~ msgstr "MS Serif" + +#~ msgid "version" +#~ msgstr "version" + +#~ msgid "by Dejan Crnila, Lars Dybdahl, and Warren Postma" +#~ msgstr "by Dejan Crnila, Lars Dybdahl, and Warren Postma" + +#~ msgid "See http://comport.sf.net/ for more info" +#~ msgstr "See http://comport.sf.net/ for more info" + +#~ msgid "Setup" +#~ msgstr "Setup" + +#~ msgid "Cancel" +#~ msgstr "Cancel" + +#~ msgid " Settings " +#~ msgstr " Settings " + +#~ msgid "Baud rate" +#~ msgstr "Baud rate" + +#~ msgid "Data bits" +#~ msgstr "Data bits" + +#~ msgid "Stop bits" +#~ msgstr "Stop bits" + +#~ msgid "Parity" +#~ msgstr "Parity" + +#~ msgid "Flow control" +#~ msgstr "Flow control" + +#~ msgid "Custom" +#~ msgstr "Custom" + +#~ msgid "None" +#~ msgstr "None" + +#~ msgid "Hardware" +#~ msgstr "Hardware" + +#~ msgid "COM11" +#~ msgstr "COM11" + +#~ msgid " ASCII Settings " +#~ msgstr " ASCII Settings " + +#~ msgid "Echo typed charachters locally" +#~ msgstr "Echo typed charachters locally" + +#~ msgid "Send line feeds with carriage return" +#~ msgstr "Send line feeds with carriage return" + +#~ msgid "Wrap lines that exceed terminal width" +#~ msgstr "Wrap lines that exceed terminal width" + +#~ msgid "Force incoming data to 7 bit ASCII" +#~ msgstr "Force incoming data to 7 bit ASCII" + +#~ msgid "Append line feeds to incoming carriage return" +#~ msgstr "Append line feeds to incoming carriage return" + +#~ msgid " Terminal Settings " +#~ msgstr " Terminal Settings " + +#~ msgid "Caret" +#~ msgstr "Caret" + +#~ msgid "Columns" +#~ msgstr "Columns" + +#~ msgid "Rows" +#~ msgstr "Rows" + +#~ msgid "Emulation" +#~ msgstr "Emulation" + +#~ msgid "Cursor keys" +#~ msgstr "Cursor keys" + +#~ msgid "Block" +#~ msgstr "Block" + +#~ msgid "Underline" +#~ msgstr "Underline" + +#~ msgid "ANSI/VT100" +#~ msgstr "ANSI/VT100" + +#~ msgid "VT52" +#~ msgstr "VT52" + +#~ msgid "Act as terminal keys" +#~ msgstr "Act as terminal keys" + +#~ msgid "Act as windows keys" +#~ msgstr "Act as windows keys" + +#~ msgid "" +#~ "Unit Test for TComPort Component and CPORTU modifications (W. Postma)" +#~ msgstr "" +#~ "Unit Test for TComPort Component and CPORTU modifications (W. Postma)" + +#~ msgid "Insert loopback plug into your serial port," +#~ msgstr "Insert loopback plug into your serial port," + +#~ msgid "select the serial port from the combo-box," +#~ msgstr "select the serial port from the combo-box," + +#~ msgid "and then click Run Test." +#~ msgstr "and then click Run Test." + +#~ msgid "Com Port" +#~ msgstr "Com Port" + +#~ msgid "Result: Not Run Yet." +#~ msgstr "Result: Not Run Yet." + +#~ msgid "Run Test" +#~ msgstr "Run Test" diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uApache.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uApache.pas new file mode 100755 index 0000000000..7f3aa7fa95 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uApache.pas @@ -0,0 +1,278 @@ +unit uApache; + +interface + +uses GnuGettext, uBaseModule, SysUtils, Classes, Windows, ExtCtrls, StdCtrls, Buttons, + uNetstatTable, uTools, uProcesses, Messages, uServices; + +type + tApacheLogType = (altAccess, altError); + + tApache = class(tBaseModule) + OldPIDs, OldPorts: string; + procedure ServiceInstall; override; + procedure ServiceUnInstall; override; + procedure Start; override; + procedure Stop; override; + procedure Admin; override; + procedure UpdateStatus; override; + procedure CheckIsService; reintroduce; + procedure EditConfig(ConfigFile: string); reintroduce; + procedure ShowLogs(LogType: tApacheLogType); reintroduce; + procedure AddLog(Log: string; LogType: tLogType=ltDefault); reintroduce; + constructor Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); + destructor Destroy; override; + end; + +implementation + +const + cServiceName = 'Apache2.2'; + cModuleName = 'apache'; + +{ tApache } + +procedure tApache.AddLog(Log: string; LogType: tLogType=ltDefault); +begin + inherited AddLog(cModuleName, Log, LogType); +end; + +procedure tApache.Admin; +var + App, Param: string; +begin + inherited; + Param:='http://localhost/xampp/'; + if Config.BrowserApp<>'' then begin + App:=Config.BrowserApp; + ExecuteFile(App,Param,'',SW_SHOW); + Addlog(Format(_('Executing "%s" "%s"'),[App,Param]),ltDebug); + end else begin + ExecuteFile(Param,'','',SW_SHOW); + Addlog(Format(_('Executing "%s"'),[Param]),ltDebug); + end; +end; + +procedure tApache.CheckIsService; +var + s: string; +begin + inherited CheckIsService(cServiceName); + if isService then s:=_('Service installed') + else s:=_('Service not installed'); + AddLog(Format(_('Checking for service (name="%s"): %s'),[cServiceName,s]),ltDebug); +end; + +constructor tApache.Create; +const Ports:array[0..1] of integer=(80,443); +var + PortBlocker: string; + ServerApp: string; + p: integer; +begin + inherited; + ModuleName:=cModuleName; + AddLog(_('Initializing module...'),ltDebug); + ServerApp:=basedir+'apache\bin\httpd.exe'; + if not FileExists(ServerApp) then + AddLog(Format(_('Possible problem detected: file "%s" not found - run this program from your XAMPP root directory!'),[ServerApp]),ltError); + + CheckIsService; + + if Config.CheckDefaultPorts then begin + AddLog(_('Checking default ports...'),ltDebug); + + for p:=Low(Ports) to High(Ports) do begin + PortBlocker:=NetStatTable.isPortInUse(Ports[p]); + if (PortBlocker<>'') then begin + if (LowerCase(PortBlocker)=LowerCase(ServerApp)) then begin + AddLog(Format(_('"%s" seems to be running on port %d?'),[ServerApp,Ports[p]]),ltError); + end else begin + AddLog(Format(_('Possible problem detected: Port %d in use by "%s"!'),[Ports[p],PortBlocker]),ltError); + end; + end; + end; + + end; +end; + +destructor tApache.Destroy; +begin + inherited; +end; + +procedure tApache.EditConfig(ConfigFile: string); +var + App, Param: string; +begin + App:=Config.EditorApp; + Param:=BaseDir+ConfigFile; + Addlog(Format(_('Executing %s %s'),[App,Param]),ltDebug); + ExecuteFile(app,param,'',SW_SHOW); +end; + + +procedure tApache.ServiceInstall; +var + App, Param: string; + RC: Integer; +begin + App:=BaseDir+'apache\bin\httpd.exe'; + Param:='-k install'; + AddLog(_('Installing service...')); + AddLog(Format(_('Executing "%s %s"'),[App, Param]),ltDebug); + RC:=RunAsAdmin(App,Param,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); +end; + +procedure tApache.ServiceUnInstall; +var + App, Param: string; + RC: Cardinal; +begin + App:=BaseDir+'apache\bin\httpd.exe'; + Param:='-k uninstall'; + AddLog(_('Uninstalling service...')); + AddLog(Format(_('Executing "%s %s"'),[App, Param]),ltDebug); + RC:=RunAsAdmin(App,Param,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); +end; + +procedure tApache.ShowLogs(LogType: tApacheLogType); +var + App, Param: string; +begin + App:=Config.EditorApp; + if LogType=altAccess then + Param:=BaseDir+'apache\logs\access.log'; + if LogType=altError then + Param:=BaseDir+'apache\logs\error.log'; + AddLog(Format(_('Executing "%s %s"'),[App,Param]),ltDebug); + ExecuteFile(app,param,'',SW_SHOW); +end; + +procedure tApache.Start; +var + App: string; + RC: Cardinal; +begin + if isService then begin + AddLog(Format(_('Starting %s service...'),[cModuleName])); + App:=Format('start "%s"',[cServiceName]); + AddLog(Format(_('Executing "%s"'),['net '+App]),ltDebug); + RC:=RunAsAdmin('net',App,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); + end else begin + AddLog(Format(_('Starting %s app...'),[cModuleName])); + App:=BaseDir+'apache\bin\httpd.exe'; + AddLog(Format(_('Executing "%s"'),[App]),ltDebug); + RunProcess(App,SW_HIDE,false); + end; +end; + +procedure tApache.Stop; +var + i, pPID: Integer; + App: string; + RC: Cardinal; +begin + if isService then begin + AddLog(Format(_('Stopping %s service...'),[cModuleName])); + App:=Format('stop "%s"',[cServiceName]); + AddLog(Format(_('Executing "%s"'),['net '+App]),ltDebug); + RC:=RunAsAdmin('net',App,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); + end else begin + if PIDList.Count>0 then begin + for i:=0 to PIDList.Count-1 do begin + pPID:=Integer(PIDList[i]); + AddLog(_('Stopping')+' '+cModuleName+' '+Format('(PID: %d)',[pPID])); + App:=Format(BaseDir+'apache\bin\pv.exe -f -k -q -i %d',[pPID]); + AddLog(Format(_('Executing "%s"'),[App]),ltDebug); + RunProcess(App,SW_HIDE,false); + end; + end else begin + AddLog(_('No PIDs found?!')); + end; + end; +end; + +procedure tApache.UpdateStatus; +var + p: Integer; + ProcInfo: TProcInfo; + s: string; + ports: string; +begin + isRunning:=false; + PIDList.Clear; + for p:=0 to Processes.ProcessList.Count-1 do begin + ProcInfo:=Processes.ProcessList[p]; + if { (pos(BaseDir,ProcInfo.ExePath)=1) and } (pos('httpd.exe',ProcInfo.Module)=1) then begin + isRunning:=true; + PIDList.Add(Pointer(ProcInfo.PID)); + end; + end; + + // Checking processes + s:=''; + for p:=0 to PIDList.Count-1 do begin + if p=0 then s:=IntToStr(Integer(PIDList[p])) + else s:=s+#13+IntToStr(Integer(PIDList[p])); + end; + if s<>OldPIDs then begin + lPID.Caption:=s; + OldPIDs:=s; + end; + + // Checking netstats + s:=''; + for p:=0 to PIDList.Count-1 do begin + ports:=NetStatTable.GetPorts4PID(Integer(PIDList[p])); + if ports<>'' then begin + if s='' then s:=ports + else s:=s+', '+ports; + end; + end; + if s<>OldPorts then begin + lPort.Caption:=s; + OldPorts:=s; + end; + + if byte(isRunning)<>oldIsRunningByte then begin + + if oldIsRunningByte<>2 then begin + if isRunning then s:=_('running') + else s:=_('stopped'); + AddLog(_('Status change detected:')+' '+s); + end; + + oldIsRunningByte:=byte(isRunning); + if isRunning then begin + pStatus.Color:=cRunningColor; + bStartStop.Caption:=_('Stop'); + bAdmin.Enabled:=true; + end else begin + pStatus.Color:=cStoppedColor; + bStartStop.Caption:=_('Start'); + bAdmin.Enabled:=false; + end; + end; + + if AutoStart then begin + AutoStart:=false; + if isRunning then begin + AddLog(_('Autostart active: modul is already running - aborted'),ltError); + end else begin + AddLog(_('Autostart active: starting...')); + Start; + end; + end; + +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uBaseModule.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uBaseModule.pas new file mode 100755 index 0000000000..f695f3c012 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uBaseModule.pas @@ -0,0 +1,93 @@ +unit uBaseModule; + +interface + +uses GnuGettext, Classes, ExtCtrls, StdCtrls, Buttons, SysUtils, uTools, uServices; + +type + tBaseModule = class + public + bbService: TBitBtn; + pStatus: tPanel; + lPID: tLabel; + lPort: tLabel; + bStartStop: tBitBtn; + bAdmin: TBitBtn; + AutoStart: boolean; + + oldIsRunningByte: byte; + isRunning: boolean; + isService: boolean; + PIDList: tList; + + ModuleName: string; + procedure Start; virtual; abstract; + procedure Stop; virtual; abstract; + procedure Admin; virtual; abstract; + procedure UpdateStatus; virtual; abstract; + + procedure ServiceInstall; virtual; abstract; + procedure ServiceUnInstall; virtual; abstract; + + procedure SetServiceButton(isActive: boolean); + procedure CheckIsService(ServiceName: string); + + procedure AddLog(module,log: string; LogType: tLogType); + constructor Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); + destructor Destroy; override; + end; + +implementation + +uses uMain; + +{ tBaseModule } + +procedure tBaseModule.Addlog(module, log: string; LogType: tLogType); +begin + fMain.AddLog(module,log,LogType); +end; + +procedure tBaseModule.CheckIsService(ServiceName: string); +var + ServiceStatus: TServiceStatus; +begin + ServiceStatus:=GetServiceStatus(ServiceName); + isService:=ServiceStatus in [ssRunning, ssStopped]; + SetServiceButton(isService); +end; + +constructor tBaseModule.Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); +begin + PIDList:=TList.Create; + isRunning:=false; + isService:=false; + + bbService:=pbbService; + pStatus:=pStatusPanel; + lPID:=pPIDLabel; + lPort:=pPortLabel; + bStartStop:=pStartStopButton; + bAdmin:=pAdminButton; + + oldIsRunningByte:=2; + + AutoStart:=false; +end; + +destructor tBaseModule.Destroy; +begin + PIDList.Free; + inherited; +end; + +procedure tBaseModule.SetServiceButton(isActive: boolean); +begin + bbService.Glyph:=nil; + if isActive then + fMain.ImageList.GetBitmap(0, bbService.Glyph) ; + if not isActive then + fMain.ImageList.GetBitmap(1, bbService.Glyph) ; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfig.dfm b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfig.dfm new file mode 100755 index 0000000000..a0624bc6da --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfig.dfm @@ -0,0 +1,341 @@ +object fConfig: TfConfig + Left = 463 + Top = 124 + Caption = 'Configuration of Control Panel' + ClientHeight = 404 + ClientWidth = 343 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + KeyPreview = True + OldCreateOrder = False + Position = poScreenCenter + OnCreate = FormCreate + OnKeyPress = FormKeyPress + OnShow = FormShow + DesignSize = ( + 343 + 404) + PixelsPerInch = 96 + TextHeight = 13 + object Label1: TLabel + Left = 8 + Top = 8 + Width = 32 + Height = 13 + Caption = 'Editor:' + end + object Label2: TLabel + Left = 8 + Top = 56 + Width = 165 + Height = 13 + Caption = 'Browser (empty = system default)' + end + object bSelectEditor: TBitBtn + Left = 308 + Top = 22 + Width = 26 + Height = 25 + Anchors = [akTop, akRight] + DoubleBuffered = True + Glyph.Data = { + 20050000424D2005000000000000360400002800000010000000100000000100 + 080001000000EA000000130B0000130B000000010000000100004E525300585B + 5D006B6F7100187A9B00197A9D000C72A500187DA1001889B1002899BF00189A + C600199AC6001B9CC70020A0C90021A2CE0025A2CF003FB8D7005BBCCE0042B2 + DE006BBFDA0042B3E20042BAEF0052BEE7004FC1E2005AC7FF006BD7FF006FD5 + FD007BDFFF0079E4F0007BE3FF00909699009AA0A300A3AAAD00ADB3B700BFC7 + CB0084D7FF0084E7FF0084EBFF0089F0F7008CF3FF0094F7FF009CF3FF0096F9 + FB009CFFFF00A5FFFF00C8D0D400F7FBFF00FFFFFF0000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000102C00000003 + 2C2120000A1F000320212C0000000003211E02000A010003021E210000000120 + 0C0500030002200000000003090B2A00091800040805011F00000004090A1B2A + 071C00051A1104001D0000000004090E0F2A0724000523140900020000000004 + 09130C2B08270004151005010000000409190925082A00041729030100000007 + 092209122E2E2D00052E0004232E060200000003092416000B090107011E0000 + 010901280526052E000409041E2100000109012E042A012E0609000320212C00 + 0000012C010D042E0003091E2100072C0000012C012C040D01200121082C0000 + 102C0001} + ParentDoubleBuffered = False + TabOrder = 0 + OnClick = bSelectEditorClick + end + object eEditor: TEdit + Left = 8 + Top = 24 + Width = 294 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 1 + end + object bSave: TBitBtn + Left = 260 + Top = 371 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Save' + DoubleBuffered = True + Glyph.Data = { + DE010000424DDE01000000000000760000002800000024000000120000000100 + 0400000000006801000000000000000000001000000000000000000000000000 + 80000080000000808000800000008000800080800000C0C0C000808080000000 + FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00333333333333 + 3333333333333333333333330000333333333333333333333333F33333333333 + 00003333344333333333333333388F3333333333000033334224333333333333 + 338338F3333333330000333422224333333333333833338F3333333300003342 + 222224333333333383333338F3333333000034222A22224333333338F338F333 + 8F33333300003222A3A2224333333338F3838F338F33333300003A2A333A2224 + 33333338F83338F338F33333000033A33333A222433333338333338F338F3333 + 0000333333333A222433333333333338F338F33300003333333333A222433333 + 333333338F338F33000033333333333A222433333333333338F338F300003333 + 33333333A222433333333333338F338F00003333333333333A22433333333333 + 3338F38F000033333333333333A223333333333333338F830000333333333333 + 333A333333333333333338330000333333333333333333333333333333333333 + 0000} + ModalResult = 1 + NumGlyphs = 2 + ParentDoubleBuffered = False + TabOrder = 2 + OnClick = bSaveClick + ExplicitTop = 335 + end + object bAbort: TBitBtn + Left = 179 + Top = 371 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Abort' + DoubleBuffered = True + Kind = bkAbort + ParentDoubleBuffered = False + TabOrder = 3 + OnClick = bAbortClick + ExplicitTop = 335 + end + object bSelectBrowser: TBitBtn + Left = 308 + Top = 69 + Width = 26 + Height = 25 + Anchors = [akTop, akRight] + DoubleBuffered = True + Glyph.Data = { + 20050000424D2005000000000000360400002800000010000000100000000100 + 080001000000EA000000130B0000130B000000010000000100004E525300585B + 5D006B6F7100187A9B00197A9D000C72A500187DA1001889B1002899BF00189A + C600199AC6001B9CC70020A0C90021A2CE0025A2CF003FB8D7005BBCCE0042B2 + DE006BBFDA0042B3E20042BAEF0052BEE7004FC1E2005AC7FF006BD7FF006FD5 + FD007BDFFF0079E4F0007BE3FF00909699009AA0A300A3AAAD00ADB3B700BFC7 + CB0084D7FF0084E7FF0084EBFF0089F0F7008CF3FF0094F7FF009CF3FF0096F9 + FB009CFFFF00A5FFFF00C8D0D400F7FBFF00FFFFFF0000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000102C00000003 + 2C2120000A1F000320212C0000000003211E02000A010003021E210000000120 + 0C0500030002200000000003090B2A00091800040805011F00000004090A1B2A + 071C00051A1104001D0000000004090E0F2A0724000523140900020000000004 + 09130C2B08270004151005010000000409190925082A00041729030100000007 + 092209122E2E2D00052E0004232E060200000003092416000B090107011E0000 + 010901280526052E000409041E2100000109012E042A012E0609000320212C00 + 0000012C010D042E0003091E2100072C0000012C012C040D01200121082C0000 + 102C0001} + ParentDoubleBuffered = False + TabOrder = 4 + OnClick = bSelectBrowserClick + end + object eBrowser: TEdit + Left = 8 + Top = 71 + Width = 294 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 5 + end + object cbDebug: TCheckBox + Left = 8 + Top = 260 + Width = 149 + Height = 17 + Caption = 'Show debug stuff' + TabOrder = 6 + OnClick = cbDebugClick + end + object cbDebugDetails: TComboBox + Left = 179 + Top = 258 + Width = 126 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + TabOrder = 7 + Items.Strings = ( + 'Details' + 'Very many details') + end + object GroupBox1: TGroupBox + Left = 8 + Top = 98 + Width = 327 + Height = 95 + Anchors = [akLeft, akTop, akRight] + Caption = ' Autostart of modules ' + TabOrder = 8 + DesignSize = ( + 327 + 95) + object Label3: TLabel + Left = 8 + Top = 65 + Width = 309 + Height = 28 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = 'Selected modules will be started on next launch of this program.' + Font.Charset = DEFAULT_CHARSET + Font.Color = clInactiveCaptionText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + WordWrap = True + end + object cbASApache: TCheckBox + Left = 8 + Top = 20 + Width = 97 + Height = 17 + Caption = 'Apache' + TabOrder = 0 + end + object cbASMySQL: TCheckBox + Left = 8 + Top = 43 + Width = 97 + Height = 17 + Caption = 'MySQL' + TabOrder = 1 + end + object cbASFileZilla: TCheckBox + Left = 145 + Top = 22 + Width = 97 + Height = 17 + Caption = 'FileZilla' + TabOrder = 2 + end + object cbASMercury: TCheckBox + Left = 145 + Top = 45 + Width = 97 + Height = 17 + Caption = 'Mercury' + TabOrder = 3 + end + end + object cbCheckDefaultPorts: TCheckBox + Left = 8 + Top = 231 + Width = 221 + Height = 17 + AllowGrayed = True + Caption = 'Check default ports on startup' + TabOrder = 9 + OnClick = cbDebugClick + end + object BitBtn1: TBitBtn + Left = 8 + Top = 292 + Width = 149 + Height = 25 + Caption = 'Change language' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 10 + OnClick = BitBtn1Click + end + object bConfigUserdefined: TBitBtn + Left = 8 + Top = 323 + Width = 149 + Height = 25 + Cancel = True + Caption = 'User defined files' + DoubleBuffered = True + ModalResult = 7 + NumGlyphs = 2 + ParentDoubleBuffered = False + TabOrder = 11 + OnClick = bConfigUserdefinedClick + end + object cbTomcatVisible: TCheckBox + Left = 8 + Top = 199 + Width = 221 + Height = 17 + AllowGrayed = True + Caption = 'Tomcat output window visible' + TabOrder = 12 + OnClick = cbDebugClick + end + object OpenDialog1: TOpenDialog + DefaultExt = 'exe' + Filter = 'Executables (*.exe)|*.exe|All files (*.*)|*.*' + Options = [ofHideReadOnly, ofPathMustExist, ofFileMustExist, ofEnableSizing] + Left = 136 + Top = 4 + end +end diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfig.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfig.pas new file mode 100755 index 0000000000..5242349f6c --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfig.pas @@ -0,0 +1,142 @@ +unit uConfig; + +interface + +uses + GnuGettext, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, Buttons, uTools; + +type + TfConfig = class(TForm) + bSelectEditor: TBitBtn; + eEditor: TEdit; + OpenDialog1: TOpenDialog; + Label1: TLabel; + bSave: TBitBtn; + bAbort: TBitBtn; + Label2: TLabel; + bSelectBrowser: TBitBtn; + eBrowser: TEdit; + cbDebug: TCheckBox; + cbDebugDetails: TComboBox; + GroupBox1: TGroupBox; + cbASApache: TCheckBox; + cbASMySQL: TCheckBox; + cbASFileZilla: TCheckBox; + cbASMercury: TCheckBox; + Label3: TLabel; + cbCheckDefaultPorts: TCheckBox; + BitBtn1: TBitBtn; + bConfigUserdefined: TBitBtn; + cbTomcatVisible: TCheckBox; + procedure bAbortClick(Sender: TObject); + procedure bSaveClick(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure bSelectEditorClick(Sender: TObject); + procedure bSelectBrowserClick(Sender: TObject); + procedure cbDebugClick(Sender: TObject); + procedure BitBtn1Click(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormKeyPress(Sender: TObject; var Key: Char); + procedure bConfigUserdefinedClick(Sender: TObject); + private + { Private-Deklarationen } + public + end; + +var + fConfig: TfConfig; + +implementation + +uses uMain, uLanguage, uConfigUserDefined; + +{$R *.dfm} + +procedure TfConfig.bSelectBrowserClick(Sender: TObject); +var dp: string; +begin + dp:=ExtractFilePath(eBrowser.Text); + if dp<>'' then begin + OpenDialog1.InitialDir:=dp; + OpenDialog1.FileName:=ExtractFileName(eBrowser.Text); + end; + if OpenDialog1.Execute then eBrowser.Text:=OpenDialog1.FileName; +end; + +procedure TfConfig.bSelectEditorClick(Sender: TObject); +var dp: string; +begin + dp:=ExtractFilePath(eEditor.Text); + if dp<>'' then begin + OpenDialog1.InitialDir:=dp; + OpenDialog1.FileName:=ExtractFileName(eEditor.Text); + end; + if OpenDialog1.Execute then eEditor.Text:=OpenDialog1.FileName; +end; + +procedure TfConfig.cbDebugClick(Sender: TObject); +begin + cbDebugDetails.Visible:=cbDebug.Checked; +end; + +procedure TfConfig.BitBtn1Click(Sender: TObject); +begin + fLanguage.ShowModal; +end; + +procedure TfConfig.bConfigUserdefinedClick(Sender: TObject); +begin + fConfigUserDefined.ShowModal; +end; + +procedure TfConfig.bSaveClick(Sender: TObject); +begin + Config.EditorApp:=Trim(eEditor.Text); + Config.BrowserApp:=Trim(eBrowser.Text); + Config.ShowDebug:=cbDebug.Checked; + Config.DebugLevel:=cbDebugDetails.ItemIndex; + Config.CheckDefaultPorts:=cbCheckDefaultPorts.Checked; + Config.TomcatVisible:=cbTomcatVisible.Checked; + + Config.ASApache:=cbASApache.Checked; + Config.ASMySQL:=cbASMySQL.Checked; + Config.ASFileZilla:=cbASFileZilla.Checked; + Config.ASMercury:=cbASMercury.Checked; + + SaveSettings; + Close; +end; + +procedure TfConfig.bAbortClick(Sender: TObject); +begin + Close; +end; + +procedure TfConfig.FormCreate(Sender: TObject); +begin + TranslateComponent(Self); +end; + +procedure TfConfig.FormKeyPress(Sender: TObject; var Key: Char); +begin + if key=#27 then begin key:=#0; bAbort.Click; end; +end; + +procedure TfConfig.FormShow(Sender: TObject); +begin + eEditor.Text:=Config.EditorApp; + eBrowser.Text:=Config.BrowserApp; + cbDebug.Checked:=Config.ShowDebug; + cbDebugDetails.Visible:=cbDebug.Checked; + cbDebugDetails.ItemIndex:=Config.DebugLevel; + cbCheckDefaultPorts.Checked:=Config.CheckDefaultPorts; + cbTomcatVisible.Checked:=Config.TomcatVisible; + + cbASApache.Checked:=Config.ASApache; + cbASMySQL.Checked:=Config.ASMySQL; + cbASFileZilla.Checked:=Config.ASFileZilla; + cbASMercury.Checked:=Config.ASMercury; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfigUserDefined.dfm b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfigUserDefined.dfm new file mode 100755 index 0000000000..8301490bcb --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfigUserDefined.dfm @@ -0,0 +1,231 @@ +object fConfigUserDefined: TfConfigUserDefined + Left = 487 + Top = 248 + Caption = 'User-defined log/config-files' + ClientHeight = 689 + ClientWidth = 557 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + Position = poScreenCenter + OnCreate = FormCreate + OnKeyPress = FormKeyPress + OnShow = FormShow + DesignSize = ( + 557 + 689) + PixelsPerInch = 96 + TextHeight = 13 + object lHeader1: TLabel + Left = 8 + Top = 8 + Width = 310 + Height = 13 + Caption = + 'Enter user defined files. Files must be relative to xampp-basedi' + + 'r!' + end + object lHeader2: TLabel + Left = 8 + Top = 27 + Width = 222 + Height = 13 + Caption = 'Example: "apache\conf\extra\httpd-info.conf"' + end + object lConfig: TLabel + Left = 17 + Top = 60 + Width = 35 + Height = 13 + Caption = 'Config' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + end + object lLogs: TLabel + Left = 280 + Top = 60 + Width = 26 + Height = 13 + Caption = 'Logs' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + end + object bSave: TBitBtn + Left = 474 + Top = 656 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Save' + DoubleBuffered = True + Glyph.Data = { + DE010000424DDE01000000000000760000002800000024000000120000000100 + 0400000000006801000000000000000000001000000000000000000000000000 + 80000080000000808000800000008000800080800000C0C0C000808080000000 + FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00333333333333 + 3333333333333333333333330000333333333333333333333333F33333333333 + 00003333344333333333333333388F3333333333000033334224333333333333 + 338338F3333333330000333422224333333333333833338F3333333300003342 + 222224333333333383333338F3333333000034222A22224333333338F338F333 + 8F33333300003222A3A2224333333338F3838F338F33333300003A2A333A2224 + 33333338F83338F338F33333000033A33333A222433333338333338F338F3333 + 0000333333333A222433333333333338F338F33300003333333333A222433333 + 333333338F338F33000033333333333A222433333333333338F338F300003333 + 33333333A222433333333333338F338F00003333333333333A22433333333333 + 3338F38F000033333333333333A223333333333333338F830000333333333333 + 333A333333333333333338330000333333333333333333333333333333333333 + 0000} + ModalResult = 1 + NumGlyphs = 2 + ParentDoubleBuffered = False + TabOrder = 0 + OnClick = bSaveClick + ExplicitTop = 544 + end + object bAbort: TBitBtn + Left = 393 + Top = 656 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Abort' + DoubleBuffered = True + Kind = bkAbort + ParentDoubleBuffered = False + TabOrder = 1 + OnClick = bAbortClick + ExplicitTop = 544 + end + object gbApache: TGroupBox + Left = 8 + Top = 79 + Width = 541 + Height = 110 + Caption = 'Apache' + TabOrder = 2 + object mConfigApache: TMemo + Left = 9 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 0 + end + object mLogsApache: TMemo + Left = 272 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 1 + end + end + object gbMySQL: TGroupBox + Left = 8 + Top = 195 + Width = 541 + Height = 110 + Caption = 'MySQL' + TabOrder = 3 + object mConfigMySQL: TMemo + Left = 9 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 0 + end + object mLogsMySQL: TMemo + Left = 272 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 1 + end + end + object gbFileZilla: TGroupBox + Left = 8 + Top = 311 + Width = 541 + Height = 110 + Caption = 'FileZilla' + TabOrder = 4 + object mConfigFilezilla: TMemo + Left = 9 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 0 + end + object mLogsFileZilla: TMemo + Left = 272 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 1 + end + end + object gbMercury: TGroupBox + Left = 8 + Top = 427 + Width = 541 + Height = 110 + Caption = 'Mercury' + TabOrder = 5 + object mConfigMercury: TMemo + Left = 9 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 0 + end + object mLogsMercury: TMemo + Left = 272 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 1 + end + end + object gbTomcat: TGroupBox + Left = 8 + Top = 543 + Width = 541 + Height = 110 + Caption = 'Mercury' + TabOrder = 6 + object mConfigTomcat: TMemo + Left = 9 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 0 + end + object mLogsTomcat: TMemo + Left = 272 + Top = 17 + Width = 261 + Height = 85 + ScrollBars = ssBoth + TabOrder = 1 + end + end +end diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfigUserDefined.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfigUserDefined.pas new file mode 100755 index 0000000000..2f0855c7b0 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uConfigUserDefined.pas @@ -0,0 +1,115 @@ +unit uConfigUserDefined; + +interface + +uses + GnuGettext, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, Buttons, uTools; + +type + TfConfigUserDefined = class(TForm) + lHeader1: TLabel; + bSave: TBitBtn; + bAbort: TBitBtn; + gbApache: TGroupBox; + mConfigApache: TMemo; + mLogsApache: TMemo; + lHeader2: TLabel; + lConfig: TLabel; + lLogs: TLabel; + gbMySQL: TGroupBox; + mConfigMySQL: TMemo; + mLogsMySQL: TMemo; + gbFileZilla: TGroupBox; + mConfigFilezilla: TMemo; + mLogsFileZilla: TMemo; + gbMercury: TGroupBox; + mConfigMercury: TMemo; + mLogsMercury: TMemo; + gbTomcat: TGroupBox; + mConfigTomcat: TMemo; + mLogsTomcat: TMemo; + procedure FormCreate(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure bAbortClick(Sender: TObject); + procedure bSaveClick(Sender: TObject); + procedure FormKeyPress(Sender: TObject; var Key: Char); + private + procedure Memo2Config; + procedure Config2Memo; + public + { Public-Deklarationen } + end; + +var + fConfigUserDefined: TfConfigUserDefined; + +implementation + +{$R *.dfm} + +{ TfConfigUserDefined } + +procedure TfConfigUserDefined.bAbortClick(Sender: TObject); +begin + Close; +end; + +procedure TfConfigUserDefined.bSaveClick(Sender: TObject); +begin + Memo2Config; + SaveSettings; +end; + +procedure TfConfigUserDefined.Memo2Config; +begin + Config.UserConfig.Apache.Text:=mConfigApache.Text; + Config.UserConfig.MySQL.Text:=mConfigMySQL.Text; + Config.UserConfig.FileZilla.Text:=mConfigFilezilla.Text; + Config.UserConfig.Mercury.Text:=mConfigMercury.Text; + Config.UserConfig.Tomcat.Text:=mConfigTomcat.Text; + + Config.UserLogs.Apache.Text:=mLogsApache.Text; + Config.UserLogs.MySQL.Text:=mLogsMySQL.Text; + Config.UserLogs.FileZilla.Text:=mLogsFileZilla.Text; + Config.UserLogs.Mercury.Text:=mLogsMercury.Text; + Config.UserLogs.Tomcat.Text:=mLogsTomcat.Text; +end; + + +procedure TfConfigUserDefined.Config2Memo; +begin + mConfigApache.Text:=Config.UserConfig.Apache.Text; + mConfigMySQL.Text:=Config.UserConfig.MySQL.Text; + mConfigFilezilla.Text:=Config.UserConfig.FileZilla.Text; + mConfigMercury.Text:=Config.UserConfig.Mercury.Text; + mConfigTomcat.Text:=Config.UserConfig.Tomcat.Text; + + mLogsApache.Text:=Config.UserLogs.Apache.Text; + mLogsMySQL.Text:=Config.UserLogs.MySQL.Text; + mLogsFileZilla.Text:=Config.UserLogs.FileZilla.Text; + mLogsMercury.Text:=Config.UserLogs.Mercury.Text; + mLogsTomcat.Text:=Config.UserLogs.Tomcat.Text; +end; + +procedure TfConfigUserDefined.FormCreate(Sender: TObject); +begin + TranslateComponent(self); +end; + +procedure TfConfigUserDefined.FormKeyPress(Sender: TObject; var Key: Char); +begin + if key=#27 then begin + key:=#0; + close; + exit; + end; +end; + +procedure TfConfigUserDefined.FormShow(Sender: TObject); +begin + Config2Memo; +end; + + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uFileZilla.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uFileZilla.pas new file mode 100755 index 0000000000..0ee5612a79 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uFileZilla.pas @@ -0,0 +1,226 @@ +unit uFileZilla; + +interface + +uses GnuGettext, uBaseModule, SysUtils, Classes, Windows, ExtCtrls, StdCtrls, Buttons, + uNetstatTable, uTools, uProcesses; + +type + tFileZilla = class(tBaseModule) + OldPIDs, OldPorts: string; + procedure ServiceInstall; override; + procedure ServiceUnInstall; override; + procedure Start; override; + procedure Stop; override; + procedure Admin; override; + procedure UpdateStatus; override; + procedure CheckIsService; reintroduce; + procedure AddLog(Log: string; LogType: tLogType=ltDefault); reintroduce; + constructor Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); + destructor Destroy; override; + end; + +implementation + +const cServiceName = 'FileZilla Server'; + cModuleName = 'FileZilla'; + +{ tFileZilla } + +procedure tFileZilla.AddLog(Log: string; LogType: tLogType); +begin + inherited AddLog('filezilla', Log, LogType); +end; + +procedure tFileZilla.Admin; +var + App: string; +begin + App:=BaseDir+'filezillaftp\filezilla server interface.exe'; + Addlog(Format(_('Executing "%s"'),[App]),ltDebug); + ExecuteFile(App,'','',SW_SHOW); +end; + +procedure tFileZilla.CheckIsService; +var + s: string; +begin + inherited CheckIsService(cServiceName); + if isService then s:=_('Service installed') + else s:=_('Service not installed'); + AddLog(Format(_('Checking for service (name="%s"): %s'),[cServiceName,s]),ltDebug); +end; + +constructor tFileZilla.Create; +var + PortBlocker: string; + ServerApp: string; + ServerPort: Integer; +begin + inherited; + ModuleName:=cModuleName; + isService:=false; + AddLog(_('Initializing module...'),ltDebug); + ServerApp:=basedir+'FileZillaFTP\FileZillaServer.exe'; + ServerPort:=21; + if not FileExists(ServerApp) then + AddLog(Format(_('Possible problem detected: file "%s" not found - run this program from your XAMPP root directory!'),[ServerApp]),ltError); + + CheckIsService; + + if Config.CheckDefaultPorts then begin + AddLog(_('Checking default ports...'),ltDebug); + PortBlocker:=NetStatTable.isPortInUse(ServerPort); + if (PortBlocker<>'') then begin + if (LowerCase(PortBlocker)=LowerCase(ServerApp)) then begin + AddLog(Format(_('"%s" seems to be running on port %d?'),[ServerApp,ServerPort]),ltError); + end else begin + AddLog(Format(_('Possible problem detected: Port %d in use by "%s"!'),[ServerPort,PortBlocker]),ltError); + end; + end; + end; +end; + +destructor tFileZilla.Destroy; +begin + + inherited; +end; + + +procedure tFileZilla.ServiceInstall; +var + App, Param: string; + RC: Integer; +begin + App:=BaseDir+'filezillaftp\filezillaserver.exe'; + AddLog(_('Installing service...')); + Addlog(Format(_('Executing "%s"'),[App]),ltDebug); + RC:=RunAsAdmin(App,Param,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); +end; + +procedure tFileZilla.ServiceUnInstall; +var + App, Param: string; + RC: Cardinal; +begin + App:='sc'; + Param:='delete "'+cServiceName+'"'; + AddLog('Uninstalling service...'); + Addlog(Format(_('Executing "%s" "%s"'),[App,Param]),ltDebug); + RC:=RunAsAdmin(App,Param,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); +end; + + +procedure tFileZilla.Start; +var + App: string; + RC: Cardinal; +begin + if isService then begin + AddLog(Format(_('Starting %s service...'),[cModuleName])); + App:=Format('start "%s"',[cServiceName]); + Addlog(Format(_('Executing "%s"'),['net '+App]),ltDebug); + RC:=RunAsAdmin('net',App,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); + end else begin + AddLog(_('FileZilla must be run as service!')); + end; +end; + +procedure tFileZilla.Stop; +var + App: string; + RC: Cardinal; +begin + if isService then begin + AddLog(Format(_('Stopping %s service...'),[cModuleName])); + App:=Format('stop "%s"',[cServiceName]); + Addlog(Format(_('Executing "%s"'),['net '+App]),ltDebug); + RC:=RunAsAdmin('net',App,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); + end else begin + AddLog(_('FileZilla must be run as service!')); + end; +end; + +procedure tFileZilla.UpdateStatus; +var + p: Integer; + ProcInfo: TProcInfo; + s: string; + ports: string; +begin + isRunning:=false; + PIDList.Clear; + for p:=0 to Processes.ProcessList.Count-1 do begin + ProcInfo:=Processes.ProcessList[p]; + if { (pos(BaseDir,ProcInfo.ExePath)=1) and } (pos('filezillaserver.exe',ProcInfo.Module)=1) then begin + isRunning:=true; + PIDList.Add(Pointer(ProcInfo.PID)); + end; + end; + + // Checking processes + s:=''; + for p:=0 to PIDList.Count-1 do begin + if p=0 then s:=IntToStr(Integer(PIDList[p])) + else s:=s+#13+IntToStr(Integer(PIDList[p])); + end; + if s<>OldPIDs then begin + lPID.Caption:=s; + OldPIDs:=s; + end; + + // Checking netstats + s:=''; + for p:=0 to PIDList.Count-1 do begin + ports:=NetStatTable.GetPorts4PID(Integer(PIDList[p])); + if ports<>'' then begin + if s='' then s:=ports + else s:=s+', '+ports; + end; + end; + if s<>OldPorts then begin + lPort.Caption:=s; + OldPorts:=s; + end; + + if byte(isRunning)<>oldIsRunningByte then begin + + if oldIsRunningByte<>2 then begin + if isRunning then s:=_('running') + else s:=_('stopped'); + AddLog(_('Status change detected:')+' '+s); + end; + + oldIsRunningByte:=byte(isRunning); + if isRunning then begin + pStatus.Color:=cRunningColor; + bStartStop.Caption:=_('Stop'); + bAdmin.Enabled:=true; + end else begin + pStatus.Color:=cStoppedColor; + bStartStop.Caption:=_('Start'); + bAdmin.Enabled:=false; + end; + end; + + if AutoStart then begin + AutoStart:=false; + if isRunning then begin + AddLog(_('Autostart active: modul is already running - aborted'),ltError); + end else begin + AddLog(_('Autostart active: starting...')); + Start; + end; + end; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uHelp.dfm b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uHelp.dfm new file mode 100755 index 0000000000..5cfb7c8a25 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uHelp.dfm @@ -0,0 +1,53 @@ +object fHelp: TfHelp + Left = 356 + Top = 94 + Caption = 'Help' + ClientHeight = 116 + ClientWidth = 256 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + Position = poScreenCenter + OnCreate = FormCreate + DesignSize = ( + 256 + 116) + PixelsPerInch = 96 + TextHeight = 13 + object lAbout: TLabel + Left = 8 + Top = 12 + Width = 233 + Height = 19 + Caption = 'programmed by Steffen Strueber' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + end + object Label1: TLabel + Left = 8 + Top = 64 + Width = 105 + Height = 13 + Caption = 'Uhm, did this help? :-)' + end + object BitBtn1: TBitBtn + Left = 173 + Top = 83 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Close' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 0 + OnClick = BitBtn1Click + end +end diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uHelp.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uHelp.pas new file mode 100755 index 0000000000..fc8afcba68 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uHelp.pas @@ -0,0 +1,39 @@ +unit uHelp; + +interface + +uses + GnuGettext, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, Buttons; + +type + TfHelp = class(TForm) + lAbout: TLabel; + Label1: TLabel; + BitBtn1: TBitBtn; + procedure BitBtn1Click(Sender: TObject); + procedure FormCreate(Sender: TObject); + private + { Private-Deklarationen } + public + { Public-Deklarationen } + end; + +var + fHelp: TfHelp; + +implementation + +{$R *.dfm} + +procedure TfHelp.BitBtn1Click(Sender: TObject); +begin + Close; +end; + +procedure TfHelp.FormCreate(Sender: TObject); +begin + TranslateComponent(Self); +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uLanguage.dfm b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uLanguage.dfm new file mode 100755 index 0000000000..1e07230ea1 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uLanguage.dfm @@ -0,0 +1,205 @@ +object fLanguage: TfLanguage + Left = 471 + Top = 128 + BorderStyle = bsToolWindow + Caption = 'Language' + ClientHeight = 136 + ClientWidth = 213 + Color = clWhite + DoubleBuffered = True + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + KeyPreview = True + OldCreateOrder = False + Position = poScreenCenter + OnCreate = FormCreate + OnKeyPress = FormKeyPress + OnShow = FormShow + DesignSize = ( + 213 + 136) + PixelsPerInch = 96 + TextHeight = 13 + object GroupBox1: TGroupBox + Left = 8 + Top = 8 + Width = 197 + Height = 89 + TabOrder = 0 + object ImgEn: TImage + Left = 12 + Top = 16 + Width = 80 + Height = 42 + AutoSize = True + Picture.Data = { + 0A544A504547496D6167654A070000FFD8FFE000104A46494600010101004800 + 480000FFDB0043000503040404030504040405050506070C08070707070F0B0B + 090C110F1212110F111113161C1713141A1511111821181A1D1D1F1F1F131722 + 24221E241C1E1F1EFFDB0043010505050706070E08080E1E1411141E1E1E1E1E + 1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E + 1E1E1E1E1E1E1E1E1E1E1E1E1EFFC0001108002A005003012200021101031101 + FFC4001F0000010501010101010100000000000000000102030405060708090A + 0BFFC400B5100002010303020403050504040000017D01020300041105122131 + 410613516107227114328191A1082342B1C11552D1F02433627282090A161718 + 191A25262728292A3435363738393A434445464748494A535455565758595A63 + 6465666768696A737475767778797A838485868788898A92939495969798999A + A2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6 + D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F01000301 + 01010101010101010000000000000102030405060708090A0BFFC400B5110002 + 0102040403040705040400010277000102031104052131061241510761711322 + 328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728 + 292A35363738393A434445464748494A535455565758595A636465666768696A + 737475767778797A82838485868788898A92939495969798999AA2A3A4A5A6A7 + A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3 + E4E5E6E7E8E9EAF2F3F4F5F6F7F8F9FAFFDA000C03010002110311003F00F13B + 3B589EC7497BBB2B48E764CE9EB185617EFF0068C1170778D800C8CF1C01F5A9 + CD9D9E752FF46B6CFF00CC57089FF12FFF00490BFE8DF3FEF38C0EFC13F5A6E8 + E863D1E348EDA7B44BB8156E2290B16D5C7DA06120F90ED2300707F84FAE2AD9 + 1266118907D8B3F651F37FC48BFD2BFE5E7E4F9FBF5F51E9B6BED9B77FEBFAFE + BE47CE25FD7F5FD7E656167679D3716D6D9FF9856513FE261FE9257FD27E7FDD + F191DB803EB505E5AC4963AB3DA595A493AA67505902A8B07FB4600B73BCEF04 + 60679E09FAD688126661890FDB71F6A1F37FC4F7FD2BFE5DFE4F93B74F43EB8A + A7AC2193489124B69EED2D2065B78A32C1B4806E0E527F906E2724727F887A62 + 84DDFF00AFEBFAF9034BFAFEBFAFC4F7FF00F857FF00017FE8A7DE7FDF49FF00 + C6E8FF00857FF017FE8A7DE7FDF49FFC6E9FFF00099FECFDFF0044EB51FF00BE + 7FFB6D1FF099FECFDFF44EB51FFBE7FF00B6D7E7DEEF91FBB7363BFE9FFF00E5 + 319FF0AFFE02FF00D14FBCFF00BE93FF008DD1FF000AFF00E02FFD14FBCFFBE9 + 3FF8DD3FFE133FD9FBFE89D6A3FF007CFF00F6DA3FE133FD9FBFE89D6A3FF7CF + FF006DA3DDF20E6C77FD3FFF00CA633FE15FFC05FF00A29F79FF007D27FF001B + A3FE15FF00C05FFA29F79FF7D27FF1BA7FFC267FB3F7FD13AD47FEF9FF00EDB4 + 7FC267FB3F7FD13AD47FEF9FFEDB47BBE41CD8EFFA7FFF0094CF27B3F867F106 + DACACEDFFE109F13CC658C25C3CBA6B3358E26DDBAD8E78240C9E9C923DEA63F + 0EBE20667FF8A0FC49FE8DFF001EFF00F12C6FF898FEFF007FFA573CFCBE99E8 + 07BD7D11FF0008BFED17FF0043DE93F927FF0019A3FE117FDA2FFE87BD27F24F + FE335EF7F6DD5FE43F3BFF0055F0FF00F4170FBDFF00F227CEE3E1D7C40CC19F + 01F893FD27FE3E3FE258DFF12EFDFEFF00F45E78F97D71D48F7A86F3E19FC41B + 9B2BCB7FF8423C4F098A3296EF169ACAD7D99B76EB939E480723AF200F7AFA3B + FE117FDA2FFE87BD27F24FFE3347FC22FF00B45FFD0F7A4FE49FFC668FEDBABF + C81FEABE1FFE82E1F7BFFE4487FE13FF008C1FF44760FF00BF0FFE347FC27FF1 + 83FE88EC1FF7E1FF00C6A6FF00845FF68BFF00A1EF49FC93FF008CD1FF0008BF + ED17FF0043DE93F927FF0019AF13DEF3FC0FABB60BFE9C7FE055087FE13FF8C1 + FF0044760FFBF0FF00E347FC27FF00183FE88EC1FF007E1FFC6A6FF845FF0068 + BFFA1EF49FC93FF8CD1FF08BFED17FF43DE93F927FF19A3DEF3FC02D82FF00A7 + 1FF815421FF84FFE307FD11D83FEFC3FF8D1FF0009FF00C60FFA23B07FDF87FF + 001A9BFE117FDA2FFE87BD27F24FFE3347FC22FF00B45FFD0F7A4FE49FFC668F + 7BCFF00B60BFE9C7FE0550E4FF00E15FFC36FF00A2E47FF02E3FFE2A8FF857FF + 000DBFE8B91FFC0B8FFF008AAF9EA8AC39D763ECBFB1F13FF4152FFC061FE47D + 0BFF000AFF00E1B7FD1723FF008171FF00F1547FC2BFF86DFF0045C8FF00E05C + 7FFC557CF5451CEBB07F63E27FE82A5FF80C3FC8FA17FE15FF00C36FFA2E47FF + 0002E3FF00E2A8FF00857FF0DBFE8B91FF00C0B8FF00F8AAF9EA8A39D760FEC7 + C4FF00D054BFF0187F91F42FFC2BFF0086DFF45C8FFE05C7FF00C551FF000AFF + 00E1B7FD1723FF008171FF00F155F3D51473AEC1FD8F89FF00A0A97FE030FF00 + 23E85FF857FF000DBFE8B91FFC0B8FFF008AA3FE15FF00C36FFA2E47FF0002E3 + FF00E2ABE7AA28E75D83FB1F13FF004152FF00C061FE47FFD9} + OnClick = RadioGroup1Click + end + object ImgDe: TImage + Left = 104 + Top = 16 + Width = 80 + Height = 42 + AutoSize = True + Picture.Data = { + 0A544A504547496D6167658F040000FFD8FFE000104A46494600010101004800 + 480000FFDB0043000503040404030504040405050506070C08070707070F0B0B + 090C110F1212110F111113161C1713141A1511111821181A1D1D1F1F1F131722 + 24221E241C1E1F1EFFDB0043010505050706070E08080E1E1411141E1E1E1E1E + 1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E1E + 1E1E1E1E1E1E1E1E1E1E1E1E1EFFC0001108002A005003012200021101031101 + FFC4001F0000010501010101010100000000000000000102030405060708090A + 0BFFC400B5100002010303020403050504040000017D01020300041105122131 + 410613516107227114328191A1082342B1C11552D1F02433627282090A161718 + 191A25262728292A3435363738393A434445464748494A535455565758595A63 + 6465666768696A737475767778797A838485868788898A92939495969798999A + A2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6 + D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F01000301 + 01010101010101010000000000000102030405060708090A0BFFC400B5110002 + 0102040403040705040400010277000102031104052131061241510761711322 + 328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728 + 292A35363738393A434445464748494A535455565758595A636465666768696A + 737475767778797A82838485868788898A92939495969798999AA2A3A4A5A6A7 + A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3 + E4E5E6E7E8E9EAF2F3F4F5F6F7F8F9FAFFDA000C03010002110311003F00F8CA + 8ADBFF0084575FFF009F0FFC8C9FFC551FF08AEBFF00F3E1FF009193FF008AAC + 3EB543F9D7DE8F5BFB0335FF00A06A9FF804BFC8C4A2B6FF00E115D7FF00E7C3 + FF002327FF001547FC22BAFF00FCF87FE464FF00E2A8FAD50FE75F7A0FEC0CD7 + FE81AA7FE012FF0023128ADBFF0084575FFF009F0FFC8C9FFC551FF08AEBFF00 + F3E1FF009193FF008AA3EB543F9D7DE83FB0335FFA06A9FF00804BFC8C4A2B6F + FE115D7FFE7C3FF2327FF1547FC22BAFFF00CF87FE464FFE2A8FAD50FE75F7A0 + FEC0CD7FE81AA7FE012FF23128ADBFF84575FF00F9F0FF00C8C9FF00C551FF00 + 08AEBFFF003E1FF9193FF8AA3EB543F9D7DE83FB0335FF00A06A9FF804BFC8F4 + FA2BDA7FE1447FD4D5FF0094FF00FED947FC288FFA9ABFF29FFF00DB2BF2DFF5 + 9B2BFF009FBF84BFC8FDFBFD63CB7FE7E7E12FF23C5A8AF69FF8511FF5357FE5 + 3FFF00B651FF000A23FEA6AFFCA7FF00F6CA3FD66CAFFE7EFE12FF0020FF0058 + F2DFF9F9F84BFC8F16A2BDA7FE1447FD4D5FF94FFF00ED947FC288FF00A9ABFF + 0029FF00FDB28FF59B2BFF009FBF84BFC83FD63CB7FE7E7E12FF0023C5A8AF69 + FF008511FF005357FE53FF00FB651FF0A23FEA6AFF00CA7FFF006CA3FD66CAFF + 00E7EFE12FF20FF58F2DFF009F9F84BFC8F16A2BDA7FE1447FD4D5FF0094FF00 + FED947FC288FFA9ABFF29FFF00DB28FF0059B2BFF9FBF84BFC83FD63CB7FE7E7 + E12FF23DA28A28AFC98FCB428A28A0028A28A0028A28A0028A28A00FFFD9} + OnClick = RadioGroup1Click + end + object rbEn: TRadioButton + Left = 46 + Top = 64 + Width = 30 + Height = 17 + TabOrder = 0 + end + object rbDe: TRadioButton + Left = 138 + Top = 64 + Width = 30 + Height = 17 + TabOrder = 1 + end + end + object bOkay: TBitBtn + Left = 130 + Top = 103 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'OK' + DoubleBuffered = True + Glyph.Data = { + DE010000424DDE01000000000000760000002800000024000000120000000100 + 0400000000006801000000000000000000001000000000000000000000000000 + 80000080000000808000800000008000800080800000C0C0C000808080000000 + FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00333333333333 + 3333333333333333333333330000333333333333333333333333F33333333333 + 00003333344333333333333333388F3333333333000033334224333333333333 + 338338F3333333330000333422224333333333333833338F3333333300003342 + 222224333333333383333338F3333333000034222A22224333333338F338F333 + 8F33333300003222A3A2224333333338F3838F338F33333300003A2A333A2224 + 33333338F83338F338F33333000033A33333A222433333338333338F338F3333 + 0000333333333A222433333333333338F338F33300003333333333A222433333 + 333333338F338F33000033333333333A222433333333333338F338F300003333 + 33333333A222433333333333338F338F00003333333333333A22433333333333 + 3338F38F000033333333333333A223333333333333338F830000333333333333 + 333A333333333333333338330000333333333333333333333333333333333333 + 0000} + ModalResult = 1 + NumGlyphs = 2 + ParentDoubleBuffered = False + TabOrder = 1 + OnClick = bOkClick + end + object bAbort: TBitBtn + Left = 49 + Top = 104 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Abort' + DoubleBuffered = True + Kind = bkAbort + ParentDoubleBuffered = False + TabOrder = 2 + OnClick = bAbortClick + end +end diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uLanguage.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uLanguage.pas new file mode 100755 index 0000000000..75376b3474 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uLanguage.pas @@ -0,0 +1,86 @@ +unit uLanguage; + +interface + +uses + GnuGettext, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, Buttons, jpeg, ExtCtrls, Registry; + +type + TfLanguage = class(TForm) + GroupBox1: TGroupBox; + ImgEn: TImage; + ImgDe: TImage; + rbEn: TRadioButton; + rbDe: TRadioButton; + bOkay: TBitBtn; + bAbort: TBitBtn; + procedure bOkClick(Sender: TObject); + procedure FormKeyPress(Sender: TObject; var Key: Char); + procedure RadioGroup1Click(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure bAbortClick(Sender: TObject); + procedure FormShow(Sender: TObject); + private + OldLang: string; + public + end; + +var + fLanguage: TfLanguage; + +implementation + +uses uTools, uMain; + +{$R *.dfm} + + +procedure TfLanguage.bAbortClick(Sender: TObject); +begin + ModalResult:=mrAbort; +end; + +procedure TfLanguage.bOkClick(Sender: TObject); +begin + if rbEn.Checked then Config.Language:='en' + else if rbde.Checked then Config.Language:='de' + else Config.Language:='en'; + ModalResult:=mrOk; + + if (OldLang<>'') and (OldLang<>Config.Language) then + MessageDlg('Restart application to apply changes!',mtInformation,[mbOk],0); +end; + + +procedure TfLanguage.FormCreate(Sender: TObject); +begin + TranslateComponent(self); +end; + +procedure TfLanguage.FormKeyPress(Sender: TObject; var Key: Char); +begin + if key=#27 then begin + key:=#0; + ModalResult:=mrAbort; + Close; + end; +end; + +procedure TfLanguage.FormShow(Sender: TObject); +begin + OldLang:=Config.Language; + if Config.Language='en' then rbEn.Checked:=true + else if Config.Language='de' then rbDe.Checked:=true + else rbEn.Checked:=true; +end; + + +procedure TfLanguage.RadioGroup1Click(Sender: TObject); +begin + if (Sender=rbEn) or (Sender=ImgEn) then rbEn.Checked:=true; + if (Sender=rbDe) or (Sender=ImgDe) then rbDe.Checked:=true; +end; + + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMain.dfm b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMain.dfm new file mode 100755 index 0000000000..10052e1c99 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMain.dfm @@ -0,0 +1,2800 @@ +object fMain: TfMain + Left = 880 + Top = 550 + Caption = 'XAMPP Control Panel v' + ClientHeight = 407 + ClientWidth = 706 + Color = clBtnFace + DoubleBuffered = True + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [] + OldCreateOrder = False + OnClose = FormClose + OnCloseQuery = FormCloseQuery + OnCreate = FormCreate + OnDestroy = FormDestroy + DesignSize = ( + 706 + 407) + PixelsPerInch = 96 + TextHeight = 13 + object imgXAMPP: TImage + Left = 6 + Top = 6 + Width = 32 + Height = 32 + AutoSize = True + Picture.Data = { + 055449636F6E0000010003004040000001002000284200003600000030300000 + 01002000A82500005E4200002020000001002000A81000000668000028000000 + 4000000080000000010020000000000000420000000000000000000000000000 + 0000000094A5BD0094A5BD009FAAB903758FB5484370B283416FB5B4366BB9B8 + 2561BBBB366BBAEC396EBDF0386EC0F0376EC1F0376FC3F0366FC3F0366FC4F0 + 366FC4F0366FC5F0366FC5F0366FC5F03670C5F03670C5F03670C5F03670C5F0 + 3670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F0 + 3670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F0 + 3670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F0366FC5F0 + 366FC5F0366FC4F0366FC4F0366FC3F0376FC3F0376EC2F0376EC0F0386EBEF0 + 3A6DBAF03369BAD43068BAB84371B5B84973B29C6283B5609BA7B80AAEB3BD00 + B0B6BF0094A5BD0097A7BE0A6082B3BD255CAEFF1957B4FF1356BAFF165ABFFF + 195DC4FF175EC7FF175FCAFF1861CDFF1861CFFF1862D0FF1962D0FF1963D1FF + 1863D2FF1863D2FF1863D2FF1863D2FF1863D2FF1963D2FF1963D2FF1963D2FF + 1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF + 1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF + 1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1863D2FF1863D2FF1863D2FF + 1863D2FF1863D2FF1863D1FF1962D1FF1862D0FF1862CFFF1860CDFF1760CBFF + 165EC9FF175DC5FF175BC1FF1356BBFF1555B5FF1E58AFFF4D74B0DDA6AFBD43 + B2B7BF007F97BD005B7FB5961450ABFF1454B3FF195BBCFF1B5FC3FF1B61C8FF + 1C63CDFF1C64D0FF1D66D3FF1D68D6FF1D68D8FF1D68D9FF1E69DAFF1E69DAFF + 1E69DAFF1E69DBFF1E69DBFF1E69DBFF1E69DBFF1E69DBFF1E69DAFF1E69DAFF + 1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF + 1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF + 1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DBFF1E69DBFF1E69DBFF1E69DBFF + 1E69DBFF1E69DAFF1E69DAFF1E69DAFF1D68D9FF1D68D8FF1D67D6FF1D66D4FF + 1C65D2FF1C63CEFF1C61CAFF1B5FC5FF195CBDFF1757B6FF0D4DACFF4C75B1D9 + 9BA9C0076F8DBB0B2C61AEE61352B0FF1A5BBBFF1B5EC3FF1C62C9FF1C64D0FF + 1D67D5FF1E68D8FF1E6ADBFF1E6BDDFF1E6CDFFF1F6CE0FF1F6DE1FF1F6DE1FF + 1F6DE1FF1F6DE1FF1F6DE1FF1F6DE1FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF + 1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF + 1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF + 1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6DE2FF1F6DE1FF + 1F6DE1FF1F6DE1FF1F6DE1FF1F6DE1FF1F6DE0FF1E6CDFFF1E6BDEFF1E6ADCFF + 1E69D9FF1D67D6FF1D65D1FF1C63CBFF1B5FC4FF1A5CBDFF1655B4FF1D57ACFF + 7692BC5E839BBF611F59AFFD1757B6FF1A5DC0FF1B61C8FF1C64D0FF1D67D7FF + 1E6ADBFF1E6BDEFF1F6DE1FF1F6EE3FF1F6FE5FF1F70E6FF1F6FE7FF2070E7FF + 2070E8FF2070E8FF2070E8FF2071E8FF2070E9FF2070E9FF2070E9FF2070E9FF + 2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF + 2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF + 2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2071E8FF2071E8FF + 2070E8FF2070E8FF2070E7FF1F70E7FF1F6FE7FF1F70E5FF1F6FE4FF1F6EE1FF + 1F6CDFFF1E6ADCFF1D68D8FF1D65D2FF1C62CBFF1A5EC2FF195AB9FF1251AEFF + 5179B5A14B76B7831C57B1FF1859BAFF1B5FC5FF1C63CDFF1D67D5FF1E6ADBFF + 1F6CDFFF1F6DE3FF1F6FE6FF2070E8FF2071EAFF2072EBFF2072ECFF2072EDFF + 2072EDFF2072EDFF2072EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF + 2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EEFF2073EEFF2073EEFF + 2073EEFF2073EEFF2073EEFF2073EEFF2073EEFF2073EDFF2073EDFF2073EDFF + 2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF + 2072EDFF2072EDFF2072EDFF2072ECFF2072ECFF2071EAFF2070E9FF1F70E7FF + 1F6EE4FF1F6DE0FF1E6ADCFF1D68D7FF1C64D0FF1B5FC7FF1A5BBDFF1252B2FF + 3E6EB3BC406EB3871755B2FF1A5BBEFF1B60C8FF1C64D1FF1E68D8FF1E6CDEFF + 1F6EE3FF206FE7FF2071EAFF2072ECFF2072EEFF2173EFFF2174EFFF2174F0FF + 2174F0FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF + 2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF + 2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF + 2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF + 2174F1FF2174F0FF2174F0FF2173F0FF2173EFFF2173EEFF2072EDFF2071EAFF + 2070E8FF1F6EE4FF1F6CDFFF1E69DAFF1D66D3FF1C61CAFF1A5DC1FF1656B6FF + 265EB0B66284B9E90F51B4FF1A5DC1FF1C62CBFF1D66D4FF1E6ADBFF1F6DE1FF + 1F70E6FF2071EAFF2073EDFF2173F0FF2174F1FF2175F2FF2175F3FF2175F4FF + 2175F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF + 2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF + 2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF + 2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF + 2176F4FF2175F4FF2175F4FF2175F3FF2175F2FF2175F2FF2174F0FF2073EEFF + 2071EBFF2070E7FF1F6EE3FF1E6BDDFF1D67D6FF1C63CDFF1B5FC4FF1657B8FF + 3063B2C8577DB9F21052B6FF1B5EC3FF1C63CDFF1D67D6FF1E6BDEFF1F6EE3FF + 2071E9FF2072ECFF2174F0FF2175F2FF2176F3FF2176F5FF2177F5FF2177F6FF + 2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F7FF2177F7FF2177F7FF + 2177F7FF2177F7FF2177F7FF2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF + 2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F7FF + 2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F6FF2177F6FF + 2177F6FF2177F6FF2177F6FF2177F5FF2176F5FF2176F4FF2175F3FF2174F0FF + 2073EDFF2071EAFF1F6FE5FF1F6CDFFF1E69D9FF1C65D0FF1B60C6FF1456BAFF + 3E6FB6F04E79B9F01254B7FF1B5FC5FF1C64CFFF1D68D8FF1E6CDFFF1F6FE5FF + 2071EAFF2173EEFF2175F1FF2176F3FF2177F5FF2177F6FF2177F7FF2177F7FF + 2177F7FF2278F7FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF + 2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF + 2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF + 2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF + 2278F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F5FF2176F4FF2175F2FF + 2173EFFF2072EBFF1F70E7FF1F6DE1FF1E69DBFF1C65D2FF1B60C8FF1558BCFF + 376BB7F04876B9F01355B9FF1B5FC6FF1C64D1FF1E69DAFF1F6DE0FF1F70E7FF + 2072EBFF2173EFFF2175F2FF2176F5FF2177F6FF2177F7FF2278F8FF2278F8FF + 2278F8FF1F76F9FF1A73F8FF1671F8FF1470F9FF136FF9FF136FF9FF1570F9FF + 1872F9FF1C74F9FF2177F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF + 2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF + 2278F9FF2077F9FF1C74F9FF1872F9FF1671F9FF1570F9FF1670F8FF1772F8FF + 1B74F8FF1F76F8FF2278F8FF2278F8FF2177F7FF2177F6FF2177F5FF2175F3FF + 2174F0FF2072EDFF2070E9FF1F6EE2FF1E6ADBFF1D66D3FF1B61C9FF1659BDFF + 3669B8F04674B9F01356BAFF1B60C7FF1C65D1FF1E69DAFF1F6DE1FF1F70E7FF + 2072ECFF2173F0FF2175F2FF2177F5FF2177F7FF2278F8FF2278F9FF2077F9FF + 1973F9FF287CF9FF4F93FAFF71A8FBFF84B4FBFF90BBFBFF8EBAFCFF7EB0FBFF + 66A2FBFF448DFAFF1E76F9FF1B74F9FF2278F9FF2279FAFF2279FAFF2279FAFF + 2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2178F9FF + 1772F9FF2077F9FF448DFAFF65A1FBFF75ABFBFF79ACFBFF78ACFBFF69A3FBFF + 4D92FAFF2C7EF9FF1973F9FF1F76F9FF2278F8FF2177F7FF2177F5FF2176F4FF + 2174F1FF2073EDFF2070E9FF1F6EE3FF1E6ADCFF1D66D4FF1C61CAFF1659BEFF + 3569B8F04473B9F01356BAFF1B60C7FF1C65D2FF1E69DBFF1F6DE1FF2070E8FF + 2072EDFF2174F0FF2175F4FF2177F6FF2177F7FF2278F8FF1B73F9FF1C75F9FF + 6AA4FBFFDAE8FCFFFAFCFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FFFFFEFFF8FBFEFFB6D2FDFF3E8AFBFF1772FAFF1F77FAFF2279FAFF2279FAFF + 2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF1B75FAFF1C75FAFF + 5C9CFBFFC0D8FDFFF8FAFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FAFCFDFFE1ECFCFF7DAFFBFF2479F9FF1973F8FF2177F7FF2177F7FF2176F4FF + 2174F1FF2073EEFF2071E9FF1F6EE3FF1E6ADCFF1D66D4FF1C61CAFF1659BEFF + 3469B8F04373B9F01356BBFF1B60C8FF1C65D2FF1E69DBFF1F6DE1FF2070E8FF + 2072EDFF2174F1FF2176F4FF2177F6FF2177F7FF1772F8FF438BFAFFBFD8FCFF + FFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFEFEFF + FFFFFEFFFFFFFEFFFFFFFEFFECF3FEFF8BB8FCFF1F77FAFF1D76FAFF2279FAFF + 2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF1572FAFF3F8AFBFFB9D4FDFF + FEFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FFFFFEFFFFFFFEFFFFFFFEFFCFE1FCFF599AFAFF1671F7FF1F76F7FF2176F5FF + 2175F1FF2073EEFF2071EAFF1F6EE3FF1E6BDDFF1D66D5FF1C61CAFF1659BFFF + 3469B8F04273B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF + 2073EDFF2174F1FF2176F4FF2177F6FF1671F7FF5999FAFFE7F0FDFFFFFFFEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFB4D1FDFF287CFAFF1D76FAFF + 2279FAFF2279FAFF2279FAFF2279FAFF1672FAFF5F9DFBFFE4EEFEFFFFFFFEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFFFFFDFFF7FAFDFF80B0FAFF1871F6FF1F75F5FF + 2175F2FF2173EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CBFF1659BFFF + 3368B9F04272B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF + 2073EDFF2174F1FF2176F4FF1670F6FF5395F8FFF0F5FDFFFFFFFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFBAD5FDFF2279FAFF + 1F77FAFF2279FAFF2279FAFF1672FAFF5B9BFCFFF2F6FEFFFFFFFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFFFFFDFFFFFFFCFF7FB0F8FF1770F4FF + 2074F2FF2173EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CBFF165ABFFF + 3368B9F04272B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF + 2073EDFF2174F1FF1B72F4FF3584F7FFDFEAFBFFFFFFFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFF9DC3FCFF + 1873FAFF2279FAFF1C76FAFF3887FBFFE6EFFEFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFFFEFCFFFBFCFBFF5E9BF6FF + 156EF2FF2173EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3368B9F04272B9F01457BBFF1B60C8FF1C65D2FF1E6ADAFF1F6EE2FF2070E9FF + 2073EDFF2074F1FF1A71F4FFA1C4F9FFFFFFFCFFFEFEFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFF9FBFEFF + 5B9CFCFF1873FAFF1B75FAFFB5D1FDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFFFFFBFFDBE7F9FF + 327FF3FF1C71EFFF2071EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3368B9F04272B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 2073EDFF1A70F1FF488DF5FFF7F8FAFFFFFEFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF + BFD9FEFF1470FBFF5497FCFFFAFCFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFFFFFF9FF + 78AAF4FF136BEFFF2071EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3368B9F04171B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 2073EDFF146DF1FF78AAF6FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFCFDFEFFC1DAFDFF9FC5FCFFA9CAFCFFDEEAFDFF + FFFFFEFFFFFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + EDF4FFFF2C7FFCFFA1C6FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFD4E5FDFFBDD7FDFFCDE0FDFFFBFCFEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFFFFEF9FF + C8DAF5FF2374EFFF1D70EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3268B9F04171B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1F72EEFF1D72F1FFB5D0F7FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFF7FAFEFF6FA7FCFF1A74FAFF1772FAFF1873FAFF2B7EFAFF + B2D0FDFFFFFFFEFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF7FB1FDFFC6DDFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FEFEFEFFFFFFFEFFF6F9FEFF7DB0FCFF237AFAFF1F77FAFF1F77FAFF5C9CFCFF + E2EDFEFFFFFFFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFFBFAF9FF + FBFAF6FF619BF0FF166BEAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3268B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1C70EEFF2F7DF2FFE8EEF8FFFFFEFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFEFF8CB9FCFF1370FAFF1F78FAFF2179FAFF2179FAFF1C75FAFF + 1E77FAFFB7D3FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFCADFFEFFEBF3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFDFEFEFF80B2FCFF126FFAFF1E77FAFF2078FAFF2078FAFF1270FAFF + 5B9BFBFFFCFDFEFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FF + FFFFF6FF95BAF1FF1469EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3268B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1B6FEEFF3C84F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFF9FAFEFF458EFBFF1B74FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1471FAFF64A2FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFDAE9FEFF257BFAFF1F77FAFF2279FAFF2279FAFF2279FAFF2178FAFF + 1B75FAFFB8D4FDFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFDF6FFA6C4F1FF196DEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3268B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE2FF2070E9FF + 1A6FEEFF458AF2FFF2F4F8FFFDFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFF1F6FEFF3082FAFF1E77FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1A74FBFF5498FCFFFDFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFC0D8FEFF2077FAFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 126FFAFF8DBAFCFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFCF6FFAEC9F2FF1C6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2070E9FF + 1A6FEEFF3F86F2FFF0F4F8FFFDFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFF4F8FEFF3D8AFBFF1D76FAFF2279FAFF2279FAFF2279FAFF2279FBFF + 1C75FBFF448EFCFFFAFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFC1D9FEFF2178FBFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1370FAFF90BCFCFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFCF6FFA9C6F2FF1B6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2070E9FF + 1B6FEEFF3982F2FFF0F4F8FFFEFCFAFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFFAFBFEFF4D93FBFF1B75FAFF2279FAFF2279FAFF2279FBFF2279FBFF + 2078FBFF2279FBFFCCE0FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFC1D9FEFF2178FBFF2078FBFF2279FAFF2279FAFF2279FAFF2279FAFF + 1370FAFF90BCFCFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFEF6FF9CBEF1FF156BEAFF1F6EE5FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 1C71EEFF2C7BF1FFE2EBF8FFFFFEFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFEFF68A3FCFF1672FAFF2279FAFF2279FAFF2279FBFF2279FBFF + 2279FBFF1873FBFF5B9BFCFFF8FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFCADEFEFF2178FBFF2078FBFF2279FBFF2279FAFF2279FAFF2279FAFF + 1370FAFF90BCFCFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFBFBFBFFFAF9F9FF + FFFDF6FF77A7F1FF136AEAFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 1F72EEFF1D72F1FFB4D0F8FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFEFEFFFFFFFEFF9CC3FCFF1672FAFF2279FAFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF1C75FBFF4D93FCFF8EBBFDFFA8CBFDFFA9CBFDFFA9CBFDFF + A9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFF + AACBFDFF83B4FDFF2279FBFF2179FBFF2279FBFF2279FAFF2279FAFF2279FAFF + 1270FAFF8EBBFCFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFBFBFBFFFEFDF9FF + DBE5F6FF307DF0FF1C6FEBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF146CF1FF82B1F6FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFEFF4FEFF3B88FBFF1B75FAFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF1A74FBFF1571FBFF1A74FBFF1A74FBFF1A74FBFF + 1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF + 1A74FBFF1C75FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FAFF2279FAFF + 1571FAFF9EC4FCFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFCFBFBFFFFFFF9FF + 83B0F5FF136BEFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF186FF1FF5999F6FFFDFCFAFFFFFEFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFFFFFFEFF92BCFCFF1571FAFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2178FBFF2178FBFF2178FBFF + 2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF + 2178FBFF2179FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FAFF1973FAFF + 3182FAFFE6F0FEFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFEFEFBFFE1EAF9FF + 3782F3FF1C70EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF1E73F1FF2C7DF5FFDCE8FAFFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFF7FAFEFF5598FBFF1672FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2078FAFF3181FBFF + B8D4FDFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFDFDFCFFFFFFFBFF88B5F8FF + 116CF2FF2173EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF2175F1FF156FF4FF80B0F9FFFFFFFCFFFEFEFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFDEEBFEFF3987FBFF1773FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF1974FAFF5E9EFBFFE7F0FEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFFFEFBFFCCDEF9FF + 2577F3FF1E72EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF2175F1FF1971F4FF468DF7FFF8FAFCFFFFFFFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFC0D9FEFF2E81FCFF1672FBFF + 2178FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF1C76FAFF3D8AFBFFE3EDFEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFEFCFBFFFBFBF9FF + 5091F4FF186FEFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF2175F1FF156FF4FF6EA6F8FFFCFDFCFFFEFEFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFD2E4FEFF5296FCFF + 1772FBFF1772FBFF1E77FBFF2178FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FAFF1571FAFF619FFBFF + FFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFBFBFBFFFFFFF9FF + 84B1F4FF146CEFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF + 2073EEFF1D72F1FF3080F5FFDEE9FAFFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFFFFFFFFFFFF + B0D0FEFF65A2FCFF3081FCFF1A74FBFF1370FBFF1571FBFF1571FBFF1571FBFF + 1571FBFF1571FBFF1571FBFF1571FBFF1571FBFF1571FBFF1672FBFF1B75FBFF + 2179FBFF2279FBFF2279FBFF2279FBFF2279FAFF2279FAFF2078FAFF1D76FAFF + BAD5FDFFFFFFFEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFFFFEF9FF + CADBF5FF2375EFFF1E70EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2070E9FF + 2073EEFF146DF1FF6AA2F6FFFFFFFBFFFEFDFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFCFDFEFFCBDFFDFFB4D2FDFFB4D2FDFFB7D4FEFF + BDD7FEFFC8DEFEFFE7F0FFFFABCCFEFF87B6FDFF78ADFDFF78ADFDFF78ADFDFF + 78ADFDFF78ADFDFF78ADFDFF78ADFDFF78ADFDFF79AEFDFF71A9FDFF4991FCFF + 1B75FBFF2078FBFF2279FBFF2279FBFF2279FAFF2279FAFF2279FAFF1672FAFF + 63A0FBFFFFFFFEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFFCFAF9FF + F7F6F6FF5191F0FF176DEAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CBFF175BC0FF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE2FF2070E9FF + 1F72EEFF186FF1FFA5C6F6FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFEBF3FEFF619EFCFF1E77FAFF1D76FAFF1D76FAFF1D76FAFF + 1571FBFF5297FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FBFFFF + 85B4FDFF1B75FBFF2178FBFF2279FAFF2279FAFF2279FAFF2279FAFF1D76FAFF + 3887FBFFF2F6FEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFEF6FF84B0F1FF1369EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF175BC0FF + 3168B9F04071B9F01458BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1C70EEFF2C7BF1FFE1EAF8FFFFFEFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFEFF7BAFFCFF0F6EFAFF2078FAFF2178FAFF2178FAFF2178FAFF + 1873FAFF5397FCFFFCFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF71A9FCFF1873FAFF2279FAFF2279FAFF2279FAFF2279FAFF2178FAFF + 2078FAFFC1D9FDFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFEF6FF9BBEF1FF166BEAFF1F6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF + 3168B9F04071B9F01458BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1B6FEEFF3A83F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFFAFBFEFF4991FBFF1A74FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1A74FAFF5497FBFFFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFB4D1FDFF1C75FAFF2178FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1471FAFF95BFFCFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FF + FFFCF6FFA8C5F1FF1A6DEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF + 3168B9F04171B9F01457BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1B6FEEFF4288F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFF2F7FEFF3383FBFF1E77FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1A74FAFF5397FBFFFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFEFFBFD9FDFF2178FAFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 126FFAFF8CBAFCFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FF + FFFCF6FFAEC9F1FF1B6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF + 3168B9F04171B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1B6FEDFF3C84F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFF6F9FEFF3D8AFBFF1B75FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1571FAFF63A0FBFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF + FFFFFEFFC7DDFDFF1F77FAFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF + 1672FAFFA1C6FCFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FF + FFFCF6FFADC8F1FF1B6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF + 3268B9F04171B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF + 1C70EDFF307EF1FFEAF0F8FFFFFDFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFEFF81B2FCFF116FFAFF2078FAFF2179FAFF2179FAFF1C75FAFF + 1D76FAFFB5D2FDFFFFFFFEFFFFFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFD2E4FFFFF1F7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFF + FFFFFEFFF0F6FEFF428DFBFF1873FAFF2279FAFF2279FAFF2279FAFF1974FAFF + 3485FAFFEAF2FEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFEF6FF9FC0F1FF166BEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3268B9F04171B9F01457BBFF1B60C8FF1C65D2FF1E6ADBFF1F6DE2FF2070E9FF + 1F72EDFF1F73F1FFBAD3F7FFFFFFFBFFFCFCFCFFFDFDFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFFFFFEFFF4F8FEFF6CA5FCFF1A74FAFF1672FAFF1873FAFF277CFAFF + ADCCFCFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF + FFFFFFFF84B5FDFFD4E5FEFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFC8DEFDFF2A7EFAFF1371FAFF1772FAFF1370FAFF2178FAFF + AFCEFDFFFFFFFEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FF + FFFEF6FF81AEF1FF136AEAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3268B9F04171B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF + 2073EDFF146CF1FF7BACF6FFFFFFFBFFFCFCFCFFFDFDFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFCFDFEFFC0D8FDFF9DC3FCFFA5C8FCFFDBE9FDFF + FFFFFEFFFFFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFF + EDF4FEFF3483FAFFB6D3FDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFD8E7FDFF8DBAFCFF619FFBFF7FB2FCFFC6DCFDFF + FFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFAFAFBFFFDFCF9FF + EAEFF6FF3C84EFFF1B6EEAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3267B9F04171B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF + 2073EDFF1A71F1FF438AF5FFF8F9FBFFFFFEFCFFFDFDFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FFFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFF + B7D3FDFF0F6EFAFF73AAFCFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFCFCFCFFFAFAFAFFFFFFF9FF + 99BDF5FF156CEFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF + 3267B9F04171B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF + 2073EDFF2074F1FF1B72F4FFAACAF9FFFFFFFCFFFEFDFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFBFCFEFF + 609EFBFF1471FAFF297EFAFFE2EDFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFCFCFCFFFDFCFBFFFAFAF9FF + 5292F3FF176EEFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CBFF165ABFFF + 3267B9F04171B9F01457BBFF1B60C8FF1C65D2FF1E69DBFF1F6EE2FF2070E9FF + 2073EDFF2174F1FF1A71F3FF3B86F6FFE6EEFBFFFFFFFDFFFDFDFDFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFA7C9FCFF + 1A74FAFF2178FAFF1873FAFF77ADFBFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFCFCFFFFFFFBFF9ABFF7FF + 1A70F2FF2073EFFF2071EAFF1F6EE3FF1E6BDDFF1D67D5FF1C62CBFF165ABFFF + 3368B9F04272B9F01456BBFF1B60C7FF1C65D2FF1E69DBFF1F6DE1FF2070E8FF + 2072EDFF2174F0FF2175F4FF1670F5FF5D9BF8FFF6F9FDFFFFFFFDFFFDFDFDFF + FDFDFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFEFEFFFFFFFEFFC8DDFDFF287CFAFF + 1E77FAFF2279FAFF2178FAFF1B75FAFFA8CAFCFFFFFFFEFFFFFFFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFDFDFFFFFFFCFFC9DCFAFF2A7BF4FF + 1C71F1FF2073EEFF2071EAFF1F6EE3FF1E6BDDFF1D66D5FF1C61CAFF165ABFFF + 3368B8F04272B9F01456BAFF1B60C7FF1C65D1FF1E69DAFF1F6DE1FF1F70E7FF + 2072ECFF2174F0FF2175F3FF2176F5FF1570F7FF66A1F9FFEFF5FCFFFFFFFDFF + FEFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFC3DAFDFF3081FAFF1B75FAFF + 2279FAFF2279FAFF2279FAFF1E77FAFF2279FAFFB2D0FDFFFFFFFEFFFFFFFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFDFDFF + FDFDFDFFFDFDFDFFFDFDFDFFFFFEFDFFFFFFFCFFCBDEFAFF3382F6FF1A71F3FF + 2174F1FF2073EDFF2071E9FF1F6EE3FF1E6ADCFF1D66D4FF1C61CAFF165ABEFF + 3368B8F04272B8F01456B9FF1B5FC6FF1C65D1FF1E69DAFF1F6DE0FF1F70E7FF + 2072ECFF2173EFFF2175F2FF2176F5FF2177F6FF1670F7FF4E92F9FFCEE0FBFF + FFFFFDFFFFFFFDFFFFFFFDFFFEFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFF + FFFEFDFFFFFFFDFFFFFFFDFFF7FAFDFF9BC1FBFF2379F9FF1C74F9FF2278F9FF + 2278F9FF2278F9FF2278F9FF2278F9FF1D75F9FF2178F9FF96BFFBFFFAFCFDFF + FFFFFDFFFFFFFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFF + FDFDFDFFFFFEFDFFFFFFFDFFFFFFFCFFB2CFFAFF2E7FF7FF1972F5FF2175F3FF + 2174F0FF2072EDFF2070E8FF1F6EE2FF1E6ADCFF1D66D3FF1B61C9FF1659BDFF + 3367B7F04372B8F01355B8FF1B5FC5FF1C64CFFF1D68D8FF1E6CDFFF1F6FE6FF + 2071EBFF2173EEFF2175F1FF2176F4FF2177F5FF2177F6FF1972F7FF267AF7FF + 83B2FAFFC8DDFBFFF0F4FCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFF + FFFEFCFFE7F0FCFFB3D0FBFF5697FAFF1872F8FF1E76F8FF2278F8FF2278F8FF + 2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF1F76F8FF1872F8FF5D9CFAFF + BED7FBFFEFF4FCFFFFFFFCFFFFFFFCFFFFFEFCFFFFFEFCFFFFFEFCFFFFFFFCFF + FFFFFCFFFBFBFCFFCBDFFAFF6DA5F8FF1A73F6FF1B73F5FF2176F4FF2175F2FF + 2173EFFF2072ECFF1F70E7FF1F6DE1FF1E69DBFF1C65D2FF1B61C8FF1658BCFF + 3267B7F04472B7F01355B7FF1B5EC4FF1C63CEFF1D68D7FF1E6BDEFF1F6FE4FF + 2070E9FF2072EDFF2174F0FF2175F2FF2176F4FF2176F5FF2177F6FF1E75F6FF + 1771F6FF2076F7FF3C87F7FF7AADF8FFA1C5F9FFB9D3FAFFB0CEFAFF96BEF9FF + 5F9DF8FF2D7FF7FF1B73F7FF1872F7FF2077F7FF2177F7FF2177F7FF2177F7FF + 2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2077F7FF1771F7FF + 1D74F7FF3A87F7FF8DB8F9FFCBDEFAFFEEF3FAFFF1F4FAFFEFF3FAFFD5E4FAFF + 9BC1F9FF5193F7FF2177F6FF1771F6FF1F76F5FF2176F4FF2175F3FF2174F1FF + 2073EEFF2071EAFF1F6FE6FF1F6CE0FF1E69D9FF1C65D1FF1B5FC7FF1658BBFF + 3267B6F04472B7F01354B5FF1A5EC2FF1C62CCFF1D67D5FF1E6ADCFF1F6EE2FF + 2070E7FF2071EBFF2173EEFF2174F1FF2175F2FF2175F3FF2176F4FF2176F5FF + 2176F5FF1F75F5FF1C73F5FF146FF5FF1770F5FF1E75F5FF1C73F5FF146FF5FF + 1770F5FF1D74F5FF2076F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF + 2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF + 1F76F5FF1B73F5FF1570F5FF2378F5FF2F7EF5FF3683F5FF307FF5FF267AF5FF + 1770F5FF1871F4FF1F75F5FF2176F4FF2176F4FF2175F3FF2174F1FF2173EFFF + 2072ECFF2070E8FF1F6EE4FF1E6BDEFF1D68D7FF1C64CFFF1B5FC5FF1658B9FF + 3367B5F04774B6F01253B2FF1A5CC0FF1B61C9FF1D65D2FF1E69DAFF1F6CDFFF + 1F6EE4FF2070E8FF2071EBFF2073EDFF2173EFFF2174F0FF2174F1FF2175F2FF + 2175F2FF2175F2FF2175F2FF2175F2FF2074F2FF1F73F2FF1F74F2FF2174F2FF + 2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF + 2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF + 2175F2FF2175F2FF2074F2FF1E73F2FF1D72F2FF1C72F2FF1D72F2FF1E73F2FF + 2074F2FF2175F2FF2175F2FF2174F1FF2175F1FF2174EFFF2173EEFF2072ECFF + 2070E9FF1F6FE6FF1F6DE1FF1E6ADBFF1D67D5FF1C62CCFF1B5EC2FF1556B6FF + 3567B4F04D77B6F01150AFFF1A5BBCFF1B5FC6FF1C64CFFF1D68D6FF1E6BDCFF + 1F6DE0FF1F6EE4FF2070E8FF2071EAFF2071EBFF1F72ECFF1E71ECFF1D71EEFF + 1D71EDFF1D70EDFF1D70EDFF1D70EDFF1D70EDFF1D70EEFF1D70EEFF1D70EEFF + 1D70EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF + 1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF + 1D71EEFF1D70EEFF1D70EEFF1D70EEFF1D70EEFF1D70EEFF1D70EDFF1D70EDFF + 1D70EDFF1D70EDFF1D71EDFF1E71EDFF1F71ECFF2072ECFF2071EBFF2071E8FF + 1F6FE5FF1F6DE1FF1E6BDDFF1D68D8FF1D65D1FF1B61C8FF1A5CBFFF1454B3FF + 3869B2F1597EB7F80F4DABFF1959B8FF1A5EC2FF1C62CAFF1D66D2FF1E68D8FF + 1E6ADCFF1F6CDFFF1F6DE2FF1D6EE4FF1C6DE5FF1F70E7FF2976EAFF347EECFF + 3C83EEFF4287EEFF468AEFFF498CF0FF4A8DF0FF4C8DF1FF4C8EF1FF4D8EF1FF + 4D8EF1FF4D8FF1FF4D8FF1FF4D8FF1FF4D8FF1FF4D8EF1FF4D8EF1FF4D8EF1FF + 4D8EF1FF4D8EF1FF4D8EF1FF4D8EF1FF4D8EF1FF4D8FF1FF4D8FF1FF4D8FF1FF + 4D8FF1FF4D8EF1FF4D8EF1FF4C8EF1FF4C8EF1FF4B8DF0FF498CF0FF478AEFFF + 4388EFFF3E84EEFF377FEDFF2C79EBFF2271E8FF1C6DE6FF1C6DE5FF1E6EE2FF + 1F6DE0FF1E6BDDFF1E69D9FF1D67D4FF1C63CCFF1B5FC4FF1A5BBBFF1351AFFF + 3D6CB0ED5E82B6B80D4AA6FF1957B3FF1A5BBCFF1A5FC5FF1B63CBFF1D65D1FF + 1D67D7FF1D68D9FF1A68DCFF2672E0FF4889EAFF659DF0FF75A8F4FF7EAEF5FF + 82B1F7FF84B2F7FF86B3F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF + 87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF + 87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF + 87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF86B3F7FF + 85B2F7FF83B1F7FF7FAEF6FF78A9F5FF6BA1F1FF5290ECFF3078E3FF1B68DCFF + 1C68DAFF1E68D8FF1D66D3FF1C63CEFF1B60C7FF1A5CBFFF1858B6FF1652ABFF + 2258A7BB255AA978164FA4FF1754ADFF1858B6FF1A5CBEFF1B5FC5FF1C62CBFF + 1A62CEFF1C65D2FF4281E0FF72A3EFFF86B2F4FF89B4F5FF87B3F5FF86B2F5FF + 86B2F5FF85B2F5FF85B2F5FF85B2F5FF85B1F5FF84B1F5FF84B1F5FF84B1F5FF + 84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF + 84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF + 84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF85B1F5FF85B2F5FF85B2F5FF + 85B2F5FF86B2F5FF86B2F5FF86B3F5FF88B4F5FF87B3F5FF7AAAF1FF528DE5FF + 236AD5FF1862CFFF1B62CCFF1B60C7FF1A5DC0FF1959B8FF1855B0FF154FA5FF + 2056A6B7527CB9881950A1FF1650A8FF1855B0FF1958B7FF1A5CBEFF195DC3FF + 2367CBFF4C86DDFF7FACF0FF89B3F3FF85B0F3FF85B1F3FF85B1F3FF85B1F3FF + 85B1F4FF85B1F4FF85B1F4FF85B2F4FF85B2F4FF86B2F4FF86B2F4FF86B2F4FF + 86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF + 86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF + 86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF85B2F4FF85B2F4FF85B1F4FF + 85B1F4FF85B1F4FF85B1F3FF85B1F3FF85B1F3FF85B1F3FF87B2F3FF86B1F2FF + 5E94E3FF2D6FD0FF1A5EC4FF1A5CBFFF1A5AB9FF1956B2FF1752AAFF0F499EFF + 416DAEBC7796C5481B509EFA144CA0FF1752A9FF1855AFFF1757B5FF1F5EBDFF + 5085D7FF7DAAECFF88B3F1FF86B2F1FF86B2F2FF86B1F2FF85B0F1FF83AFF1FF + 82AEF1FF81AEF0FF81ADF0FF80ACF0FF7FACF0FF7FACF0FF7FABEFFF7EABEFFF + 7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7FACEFFF7FACEFFF + 7FACEFFF7FACEFFF7FACEFFF7FACEFFF7FACEFFF7EABEFFF7EABEFFF7EABEFFF + 7EABEFFF7EABEFFF7EABEFFF7FABEFFF7FABEFFF7FACF0FF80ACF0FF80ADF0FF + 81AEF0FF82AEF0FF83AFF1FF84B0F1FF85B1F2FF86B1F2FF86B1F1FF87B2F1FF + 84B0EFFF6294DFFF2866C2FF1757B6FF1855B1FF1853AAFF174FA3FF0C4599FF + 5F83B9A196AED2033A66A8E80C4496FF164EA1FF1751A7FF1753ACFF1B58B2FF + 2B66BEFF3871C6FF3F77CCFF417ACEFF4079CEFF3D77CEFF3974CDFF3571CBFF + 326FCAFF2F6DC9FF2E6CC9FF2C6BC8FF2B6AC8FF2A69C8FF2A69C7FF2968C7FF + 2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF + 2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF + 2968C7FF2968C7FF2969C7FF2969C7FF2A69C7FF2A6AC8FF2B6AC8FF2D6CC9FF + 2F6DC9FF316ECAFF3471CBFF3873CCFF3C77CDFF3F78CEFF4179CEFF4078CCFF + 3A73C8FF2F69C1FF1E5BB4FF1753ADFF1751A8FF174FA2FF114799FF1F519CFC + B4C2D95CD0D9E500839FC97B0E4494FF0D4394FF134A9DFF164EA2FF1751A6FF + 1450A8FF1350AAFF1350ADFF1251AEFF1352B0FF1352B1FF1453B2FF1453B2FF + 1554B3FF1555B4FF1555B4FF1555B4FF1555B4FF1655B4FF1655B4FF1655B4FF + 1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF + 1655B4FF1656B4FF1656B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF + 1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1555B4FF1555B4FF1555B4FF + 1555B4FF1555B3FF1454B3FF1453B2FF1353B1FF1352B0FF1251AFFF1351ADFF + 1350ABFF1450A9FF1650A7FF164FA3FF154C9EFF114797FF0F4493FF5B7FB8A6 + CED7E400C5D1E2009EB5D8006589C17C3562A5E01C4F9BFF184C9BFF154B9DFF + 0A449AFF0C469DFF0F49A0FF104BA2FF114CA3FF124DA4FF124DA5FF124DA6FF + 124EA6FF124EA6FF124EA6FF124EA6FF124EA6FF124EA6FF134EA7FF134EA7FF + 134EA7FF134FA7FF134FA7FF134FA7FF134FA7FF134FA7FF134FA7FF134FA8FF + 134FA8FF134FA7FF134FA7FF134EA7FF134EA7FF134EA7FF134EA7FF134EA7FF + 134EA7FF134EA7FF134EA7FF134EA7FF124EA6FF124EA6FF124EA6FF124EA6FF + 124EA6FF124EA6FF124DA6FF124DA5FF124DA4FF114CA4FF104BA2FF0F4AA0FF + 0E479DFF0A449BFF0E469BFF184E9DFF184C99FF22539CEE4A74B4A59AB2D70D + A4BAD900C5D1E20094AED40097B0D60083A2CF0B86A2CE604775B9801C51A086 + 6487BAE9597FB9F04973B4F0406EB2F03C6BB1F03A69B1F03968B0F03767B0F0 + 3767B0F03667B0F03666B0F03666B0F03666B0F03666B0F03566B0F03466B0F0 + 3466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F0 + 3466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F0 + 3466B0F03466B0F03466B0F03466AFF03666B0F03666B0F03666B0F03666B0F0 + 3666B0F03767B0F03767B0F03868B1F03A69B0F03C6BB1F03F6EB2F04773B4F0 + 537BB7F06D8DBEF04570AFB82F62AF805E86BF606D90C60F6D90C80094ADD300 + A8BCDA00F000000000000007C000000000000003800000000000000180000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000080000000 + 0000000080000000000000008000000000000001C000000000000003F0000000 + 0000000F28000000300000006000000001002000000000008025000000000000 + 00000000000000000000000097A7BD0091A2BC006B89B4544671B38F3D6EB7B8 + 2763BCB82965BED43A6FC0F0376FC3F0366FC4F03770C5F0376FC5F0376FC6F0 + 3770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F0 + 3770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F0 + 3770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F0376FC5F0 + 3770C5F0376FC4F0376FC3F0396EC0F03069BEE02864BDB83D6EB8B8416FB39C + 6C89B36D9CA8BC07B4B8BF008EA1BE006486B58A1C57AEFF1657B7FF155AC0FF + 1A5FC7FF1A61CCFF1862D0FF1863D3FF1964D4FF1964D5FF1964D6FF1964D6FF + 1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF + 1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF + 1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1964D6FF + 1964D5FF1964D5FF1863D3FF1862D1FF1962CDFF195FC9FF155AC2FF1557BAFF + 1755B0FF5B7FB4BFA9B2C007718FBB2A2059AEFC1455B6FF1A5EC2FF1C62CBFF + 1D66D3FF1D68D8FF1E6ADBFF1F6BDDFF1E6CDFFF1E6CDFFF1E6DE0FF1E6DE0FF + 1E6DE0FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF + 1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF + 1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE0FF1E6DE0FF1E6DE0FF + 1E6CDFFF1E6CDFFF1F6BDEFF1E6ADBFF1E69D8FF1D66D4FF1C63CCFF1B5FC4FF + 1658B9FF1753ADFF6F8DB95E6889BA741755B1FF195BBEFF1C62CAFF1D66D3FF + 1E6ADBFF1F6DDFFF1F6EE3FF1F6FE5FF2070E7FF2070E7FF2071E8FF2071E8FF + 2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF + 2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF + 2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E8FF2071E8FF + 2071E8FF2070E7FF1F70E6FF1F6EE4FF1F6DE0FF1E6ADCFF1D67D5FF1C63CCFF + 1B5DC1FF1152B2FF4B76B5A13869B3821857B6FF1B5FC4FF1C64D0FF1E69DAFF + 1F6DE1FF2070E6FF2071EAFF2072EDFF2173EEFF2173EEFF2173EFFF2173F0FF + 2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF + 2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF + 2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173EFFF + 2173EFFF2173EEFF2072EDFF2072EBFF2070E7FF1F6EE2FF1E6ADCFF1D65D3FF + 1B61C7FF1557B8FF3165B3B95D82B9D11053B8FF1C61C9FF1D67D5FF1E6CDEFF + 1F70E5FF2072EBFF2173EEFF2175F1FF2175F2FF2176F3FF2176F3FF2176F4FF + 2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF + 2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF + 2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F3FF + 2176F3FF2175F3FF2175F2FF2173EFFF2072ECFF2070E6FF1F6DE0FF1D68D7FF + 1C62CBFF1759BCFF3065B3C8557EBAF51155BCFF1C63CCFF1D68D8FF1F6DE1FF + 2071E8FF2173EEFF2174F2FF2176F4FF2176F6FF2177F6FF2177F6FF2177F6FF + 2177F6FF2177F6FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF + 2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF + 2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F6FF2177F6FF2177F6FF + 2177F6FF2176F6FF2176F4FF2175F3FF2173EFFF2071EAFF1F6EE2FF1E6ADAFF + 1C64CFFF1559BFFF3E6FB7F04B77BAF01457BEFF1C63CEFF1E69DAFF1F6EE2FF + 2072EBFF2174EFFF2176F3FF2177F6FF2177F7FF2278F8FF2177F8FF1E76F8FF + 1872F8FF1470F8FF126FF8FF136FF8FF1771F8FF1D75F8FF2177F9FF2278F9FF + 2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF + 2178F8FF1E76F8FF1973F8FF1671F8FF1570F8FF1671F8FF1A73F8FF1E75F8FF + 2177F8FF2177F7FF2177F6FF2176F5FF2175F1FF2072ECFF1F6FE4FF1E6ADCFF + 1C65D1FF165AC2FF376AB8F04674BAF01458BEFF1C63CFFF1E6ADBFF1F6FE4FF + 2072EBFF2175F1FF2176F5FF2177F7FF2278F8FF1E76F9FF1B74F9FF3080FAFF + 5E9CFBFF80B1FBFF90BBFBFF89B7FCFF6DA6FBFF3B88FAFF1B75FAFF1D76F9FF + 2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2077FAFF + 1973F9FF2E7FFAFF5799FBFF73A9FBFF7AADFBFF71A8FBFF4F94FAFF2C7EF9FF + 1A73F9FF1F76F8FF2177F7FF2177F5FF2175F2FF2073EDFF1F6FE6FF1E6BDDFF + 1D65D2FF175CC3FF3469B9F04473BAF01458BFFF1C64D0FF1E6ADBFF1F6FE4FF + 2072ECFF2175F1FF2177F5FF2177F7FF1973F8FF287CFAFFA1C5FCFFE8F1FDFF + FFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFF0F6FEFFA9CBFDFF2B7FFBFF + 1973FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF1C75FAFF1E76FAFF + 88B7FCFFE2EDFEFFFDFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFBFCFEFFE5EFFDFF + 90BBFBFF2077F8FF1A73F7FF2177F6FF2175F2FF2073EEFF2070E6FF1E6BDEFF + 1D65D2FF175CC3FF3369B9F04473BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF + 2072EDFF2175F2FF2177F5FF1872F7FF3D89F9FFC6DCFDFFFFFFFEFFFFFFFEFF + FFFFFEFFFFFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFCDE0FDFF + 438DFBFF1873FAFF2279FAFF2279FAFF2279FAFF1A75FAFF3082FBFFB2D1FDFF + FFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FFFFFEFFBBD5FCFF3684F8FF1871F6FF2176F3FF2172EEFF2070E6FF1E6BDEFF + 1D66D3FF175CC4FF3269BAF04373BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF + 2072EDFF2175F2FF1972F5FF3F89F9FFDFEBFDFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFF + E6F0FEFF488FFBFF1974FAFF2379FAFF1C76FAFF3383FBFFD0E2FDFFFFFFFEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFDFFD7E6FCFF3985F7FF1A72F3FF2173EEFF2070E7FF1E6CDEFF + 1D66D3FF175CC4FF3269BAF04273BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF + 2072EDFF1E73F2FF2579F6FFCCDFFBFFFFFFFDFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFD8E7FEFF2E80FBFF1B75FAFF2078FAFFBED7FDFFFFFFFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFDFDFDFFFFFFFCFFC6DBFAFF2478F3FF1E72EEFF2070E7FF1E6CDEFF + 1D66D3FF175CC4FF3268BAF04273BAF01559C1FF1C65D0FF1E6ADCFF1F6FE5FF + 2072EDFF166EF2FF80B0F7FFFFFFFCFFFEFEFDFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFFFF93BDFEFF0B6BFAFF74ABFCFFFFFFFFFFFFFFFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFDFDFDFFFDFDFCFFFFFFFBFF7EAEF5FF166DEEFF2070E7FF1E6CDEFF + 1D66D3FF175DC4FF3268BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 1E71EDFF2577F2FFD4E4F9FFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFF + FFFFFEFFD1E3FDFF9FC4FCFFBBD5FDFFFDFDFEFFFFFFFEFFFEFEFEFFFFFFFFFF + FFFFFFFFFFFFFFFFE7F0FFFF3686FCFFCDE1FFFFFFFFFFFFFFFFFFFFFEFEFEFF + FEFEFEFFFFFFFEFFFFFFFEFFD9E8FDFFBED8FDFFE6EFFEFFFFFFFEFFFEFEFEFF + FEFEFEFFFDFDFDFFFCFCFDFFFFFFFBFFD6E3F7FF2677EEFF1D6FE7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 196EEDFF498DF3FFF7F8FAFFFEFEFCFFFDFDFDFFFEFEFEFFFFFFFEFFFFFFFEFF + A7CAFDFF2379FBFF1572FAFF1974FAFF6EA7FCFFF7FAFEFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFAECEFEFFEFF5FFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFEFFFEFFFEFF91BCFCFF257BFBFF1D76FAFF3081FBFFB5D2FDFFFFFFFEFF + FFFFFEFFFDFDFDFFFDFDFDFFFEFCFBFFF7F8F7FF4F8FEFFF186CE7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 156DECFF679FF4FFFEFCFAFFFDFCFCFFFDFDFDFFFEFEFEFFFFFFFEFFE9F1FEFF + 3082FAFF1974FAFF2279FAFF2077FAFF1471FAFFAECEFEFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFF8FBFFFFFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFC2DAFEFF1C75FAFF1C76FAFF2078FAFF1973FAFF2E80FAFFD9E7FDFF + FFFFFEFFFEFEFEFFFDFDFDFFFBFBFBFFFEFCF7FF72A4EFFF1469E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D1FF1E6BDCFF1F6FE5FF + 146BECFF73A7F4FFFFFEFAFFFDFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFC3DAFDFF + 2078FAFF2078FAFF2279FAFF2279FAFF126FFAFF87B6FDFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF91BCFDFF1270FAFF2279FAFF2279FAFF2279FAFF116FFAFF90BCFCFF + FFFFFEFFFEFEFEFFFCFCFCFFFBFBFBFFFFFEF7FF7FADF0FF1268E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE6FF + 156CEDFF6DA3F4FFFFFEFAFFFEFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFD1E2FDFF + 257BFAFF2078FAFF2279FAFF2279FBFF1773FBFF6BA6FDFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF90BCFDFF1270FBFF2279FAFF2279FAFF2279FAFF1370FAFF8FBBFCFF + FFFFFEFFFEFEFEFFFCFCFCFFFBFBFBFFFFFDF7FF78A8F0FF1369E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE6FF + 166CEDFF639DF4FFFDFCFAFFFEFDFCFFFEFEFEFFFEFEFEFFFFFFFEFFEBF3FEFF + 2E80FAFF1F77FAFF2279FBFF2279FBFF1E77FBFF3483FCFFEBF3FFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF95BEFDFF1170FBFF2279FBFF2279FAFF2279FAFF1370FAFF90BCFCFF + FFFFFEFFFEFEFEFFFCFCFCFFFDFBFBFFFBFAF7FF619AF0FF166AE7FF1E6CDFFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF + 196EEDFF488CF3FFF6F8FAFFFFFEFCFFFEFEFEFFFEFEFEFFFFFFFEFFFCFDFEFF + 5497FBFF1873FAFF2279FBFF2279FBFF2279FBFF1B75FBFF5598FCFFB7D3FEFF + CDE1FEFFCEE1FEFFCDE1FEFFCDE1FEFFCDE1FEFFCDE1FEFFCDE1FEFFCDE1FEFF + CEE1FEFF78ADFDFF1672FBFF2279FBFF2279FAFF2279FAFF126FFAFF8DBAFCFF + FFFFFEFFFEFEFEFFFCFCFCFFFFFDFBFFEBF1F7FF3881EFFF1B6DE7FF1E6CDFFF + 1D66D4FF175DC4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF + 1D71EDFF2A7AF3FFDFEAFAFFFFFFFCFFFEFEFEFFFEFEFEFFFFFEFEFFFFFFFEFF + A5C8FDFF1672FAFF2279FBFF2279FBFF2279FBFF2279FBFF1A74FBFF1E77FBFF + 257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF + 257BFBFF237AFBFF2279FBFF2279FBFF2279FBFF2178FAFF1471FAFFA7CAFDFF + FFFFFEFFFEFEFEFFFDFCFCFFFFFFFBFFAAC8F7FF196EEEFF1F70E7FF1E6CDFFF + 1C66D4FF175DC5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF + 2072EDFF1970F2FFA8C9F9FFFFFFFCFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFF + F6FAFEFF4F94FBFF1772FBFF2279FBFF2279FBFF2279FBFF2279FBFF2078FBFF + 2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF + 2078FBFF2178FBFF2279FBFF2279FBFF2179FBFF1471FAFF5E9EFCFFF6FAFEFF + FFFFFEFFFEFEFEFFFFFFFDFFF0F4FAFF458BF4FF196FEFFF2070E7FF1E6CDFFF + 1C66D4FF175DC5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF + 2072EDFF176FF2FF5A99F7FFF9FBFCFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFD4E5FEFF3081FCFF1874FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2178FBFF2179FBFF8DBAFCFFF2F7FEFFFFFFFEFF + FEFEFEFFFEFEFEFFFFFFFDFFEFF4FAFF3F87F4FF1A6FEFFF2070E7FF1E6CDFFF + 1C66D4FF175DC5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF + 2072EDFF1E73F3FF2679F6FFDFEAFCFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFCADFFFFF3A88FCFF1370FBFF1E77FBFF2178FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF + 2279FBFF2279FBFF2279FBFF2279FBFF1B75FAFF87B7FDFFFFFFFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFDFCFCFFFFFFFBFF8DB7F6FF156DEEFF2070E7FF1E6CDFFF + 1D66D4FF175DC5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF + 2072EDFF176FF2FF6BA4F7FFFAFCFCFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFEFFFFFFFFFFEFF5FFFF83B4FDFF3383FCFF1974FBFF1370FBFF + 1571FBFF1571FBFF1571FBFF1571FBFF1571FBFF1571FBFF1571FBFF1873FBFF + 1F77FBFF2279FBFF2279FBFF2279FBFF2078FAFF1E77FAFFC4DBFEFFFFFFFEFF + FEFEFEFFFEFEFEFFFCFCFCFFFFFFFBFFCDDDF7FF2375EFFF1E6FE7FF1E6CDFFF + 1D66D4FF175DC4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE6FF + 1E72EDFF2175F2FFC9DCF9FFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFF + FFFFFEFFD8E7FDFFB4D2FDFFBAD5FDFFB7D3FEFFB9D4FEFFAACCFEFF89B7FDFF + 78ADFDFF78ADFDFF78ADFDFF78ADFDFF78ADFDFF79ADFDFF79AEFDFF63A1FDFF + 277CFCFF1E77FBFF2279FBFF2279FAFF2279FAFF1571FAFF6AA5FCFFFFFFFEFF + FFFFFEFFFEFEFEFFFCFCFCFFFEFDFBFFF0F3F7FF3C84EFFF1A6DE7FF1E6CDFFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D1FF1E6BDCFF1F6FE5FF + 1A6FEDFF4389F3FFF4F6FAFFFFFEFCFFFDFDFDFFFEFEFEFFFFFFFEFFFFFFFEFF + 98C0FDFF237AFAFF1C76FAFF1D76FAFF106EFAFF88B7FDFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + C2DAFEFF2A7EFBFF1F77FBFF2279FAFF2279FAFF1E77FAFF3182FAFFE7F0FEFF + FFFFFEFFFEFEFEFFFCFCFCFFFCFBFBFFFAF9F7FF5F99EFFF166AE7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04071BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 166CECFF649DF4FFFDFCFAFFFDFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFEBF3FEFF + 2E80FAFF1A74FAFF2178FAFF2178FAFF126FFAFF84B4FDFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF70A8FCFF1772FAFF2279FAFF2279FAFF2279FAFF1973FAFFA7CAFDFF + FFFFFEFFFEFEFEFFFDFDFDFFFBFBFBFFFFFCF7FF74A5EFFF1369E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 146BECFF70A4F4FFFFFDFAFFFDFCFCFFFDFDFDFFFEFEFEFFFFFFFEFFC9DEFDFF + 2279FAFF2078FAFF2279FAFF2279FAFF1370FAFF82B4FCFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF8DBAFCFF136FFAFF2279FAFF2279FAFF2279FAFF116FFAFF8BB9FCFF + FFFFFEFFFDFDFDFFFDFDFDFFFBFBFBFFFFFEF7FF7EACF0FF1268E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 156BECFF6CA3F4FFFFFDFAFFFDFCFCFFFDFDFDFFFEFEFEFFFFFFFEFFD6E6FDFF + 2379FAFF1E77FAFF2279FAFF2279FAFF126FFAFF9BC3FDFFFFFFFEFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFEFF9AC2FCFF1270FAFF2279FAFF2279FAFF2178FAFF1672FAFFAACCFDFF + FFFFFEFFFDFDFDFFFDFDFDFFFBFBFBFFFFFEF7FF7DABF0FF1268E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF + 186DEDFF5494F3FFFAFAFAFFFEFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFFEFEFEFF + 7AAEFCFF126FFAFF1571FAFF116FFAFF468EFBFFE8F1FEFFFFFFFEFFFEFEFEFF + FFFFFFFFFFFFFFFFFFFFFFFFD6E6FFFFF5F9FFFFFFFFFFFFFEFEFEFFFEFEFEFF + FFFFFEFFDFECFDFF3585FBFF1370FAFF1B75FAFF0F6EFAFF5D9CFCFFF8FBFEFF + FFFFFEFFFDFDFDFFFCFCFCFFFCFBFAFFFDFBF7FF699FEFFF156AE7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF0155AC1FF1C65D0FF1E6ADCFF1F6FE5FF + 1C70EDFF2F7DF2FFE5EDF9FFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFF + FAFCFEFFA2C6FDFF6BA6FCFF87B6FCFFE4EEFEFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFF3F7FEFF5E9DFBFFE8F1FEFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFD0E3FDFF65A2FCFF468FFBFF77ACFCFFE8F1FEFFFFFFFEFF + FEFEFEFFFDFDFDFFFCFCFCFFFEFDFAFFF0F3F7FF3E85EEFF1A6DE7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04172BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF + 2072EDFF186FF2FF9EC2F8FFFFFFFCFFFEFDFDFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFFFFFEFFAECEFDFF116FFAFFB1CFFDFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFF8FBFEFFFFFFFEFFFFFFFEFFFEFEFEFF + FEFEFEFFFDFDFDFFFCFCFCFFFFFFFAFFBAD2F6FF1C71EEFF1E70E7FF1E6CDEFF + 1D66D3FF175DC4FF3168BAF04272BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF + 2072EDFF1B71F2FF3C86F6FFEBF1FCFFFFFFFDFFFDFDFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFFFFFEFFFFFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FFFFFEFFF3F8FEFF478FFBFF116FFAFF4E93FBFFF8FBFEFFFFFFFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFF + FEFEFEFFFDFDFDFFFFFEFCFFF8F9FAFF5494F4FF186DEEFF2070E7FF1E6BDEFF + 1D66D3FF175CC4FF3268BAF04272BAF01559C0FF1C64D0FF1E6ADBFF1F6FE4FF + 2072ECFF2175F2FF1670F5FF67A1F9FFFBFCFDFFFFFFFDFFFDFDFEFFFEFEFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFF + FFFFFEFF74ABFCFF1773FAFF2279FAFF1772FAFF87B6FCFFFFFFFEFFFFFFFEFF + FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF + FDFDFDFFFFFFFDFFFFFFFCFF89B6F8FF176FF2FF2073EEFF2070E6FF1E6BDEFF + 1D66D3FF175CC3FF3268BAF04272BAF01558BFFF1C63CFFF1E6ADBFF1F6FE4FF + 2072EBFF2175F0FF2076F5FF1771F6FF6EA6FAFFF0F6FDFFFFFFFDFFFFFFFDFF + FDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFDFDFEFFFFFFFEFFFFFFFEFFF4F8FEFF + 77ACFCFF1973FAFF2179FAFF2279FAFF2078FAFF1B75FAFF92BDFCFFFFFFFEFF + FFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFF + FFFFFDFFFFFFFDFF91BBF9FF1B73F5FF1F74F2FF2073EDFF1F6FE6FF1E6BDDFF + 1D65D2FF175CC3FF3268B9F04272B9F01458BEFF1C63CEFF1E69DAFF1F6EE3FF + 2071EBFF2174F0FF2176F3FF2076F6FF1671F7FF498FF8FFB7D2FBFFF8FAFCFF + FFFFFDFFFFFFFDFFFFFFFDFFFFFFFDFFFFFFFDFFFFFFFDFFBCD6FCFF4E93FAFF + 1771F9FF2177F9FF2278F9FF2278F9FF2278F9FF1F77F9FF1A73F9FF6AA4FAFF + DCE9FCFFFFFFFDFFFFFFFDFFFFFFFCFFFFFEFDFFFFFFFDFFFFFFFDFFFFFFFDFF + DCE9FBFF6BA4F9FF1972F6FF1E74F4FF2175F1FF2072ECFF1F6FE4FF1E6ADCFF + 1D64D1FF175BC2FF3268B8F04372B8F01357BCFF1C63CCFF1D68D9FF1F6DE1FF + 2071E9FF2173EEFF2174F2FF2176F5FF2177F6FF1972F6FF1B73F6FF4C90F8FF + A1C5FAFFC5DAFAFFD4E3FAFFCBDEFAFFB0CEFAFF65A1F8FF1C74F7FF1872F7FF + 2178F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2077F7FF1570F7FF + 3181F8FF96BEFAFFD3E3FAFFECF2FBFFF1F5FBFFEAF0FBFFC9DDFAFF96BEF9FF + 3281F7FF1570F5FF2075F4FF2175F3FF2174EFFF2071EAFF1F6EE3FF1E6ADBFF + 1C63CFFF175AC0FF3268B7F04473B7F01356BAFF1C61CAFF1D67D6FF1E6CDFFF + 2070E6FF2072ECFF2174F0FF2175F2FF2176F3FF2176F4FF2075F5FF1972F5FF + 1770F5FF2277F5FF267AF5FF2478F5FF1B73F5FF1770F5FF1F76F5FF2177F5FF + 2176F5FF2176F5FF2176F5FF2176F5FF2176F5FF2176F5FF2177F5FF2177F5FF + 1C73F5FF1871F5FF2679F5FF2E7FF5FF3582F5FF2D7EF5FF2277F5FF1770F4FF + 1C73F4FF2176F4FF2175F2FF2174F0FF2072EDFF1F71E8FF1F6DE1FF1D68D8FF + 1C63CDFF165ABEFF3368B6F04975B7F01354B6FF1B60C6FF1D65D2FF1E6ADBFF + 1F6EE2FF2071E8FF2072EBFF2072EEFF1F73EFFF1E72F0FF1E73F0FF1D72F0FF + 1C71F0FF1B70F0FF1A70F0FF1A70F0FF1B71F0FF1C72F0FF1D72F0FF1C72F0FF + 1C72F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF + 1D72F0FF1C71F0FF1A70F0FF196FF0FF186FF0FF1970F0FF1B71F0FF1D72F0FF + 1F73F0FF1F73EFFF2073EFFF2072ECFF2071E9FF1F6EE4FF1E6BDDFF1D66D4FF + 1C61C9FF1557BAFF3669B4F1587FB8F81050B0FF1B5EC1FF1C63CDFF1D67D6FF + 1E6BDDFF1F6EE2FF1D6DE5FF1B6DE7FF2071E9FF2A78ECFF317CEEFF3680EEFF + 3982EEFF3B83EFFF3C84EFFF3C84EFFF3D84EFFF3D85EFFF3D85EFFF3D85EFFF + 3D84EFFF3D84EFFF3D84EFFF3D84EFFF3D84EFFF3D84EFFF3D85EFFF3D85EFFF + 3D85EFFF3D84EFFF3C84EFFF3C84EFFF3B83EFFF3982EEFF3680EEFF327DEEFF + 2C79ECFF2273EAFF1C6DE7FF1C6DE5FF1F6EE2FF1F6CDEFF1E68D7FF1C64CFFF + 1A5EC4FF1455B5FF3B6BB2ED4C75B2B8104EAAFF195ABAFF1B5FC5FF1C64CEFF + 1C67D5FF1966D9FF2871E0FF4D8CEBFF6AA0F2FF79ABF5FF80AEF6FF82B1F6FF + 84B2F6FF84B2F6FF84B2F6FF84B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF + 85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF + 85B2F6FF85B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF83B1F6FF80AFF6FF + 7BACF5FF6FA3F2FF5592ECFF2F77E2FF1A67DAFF1C66D6FF1D65CFFF1B60C7FF + 1A5CBCFF1654AFFF2258A8BB3062AC7C1650A5FF1856B2FF195BBCFF1A5EC4FF + 1B62CBFF3F7EDCFF76A6EFFF89B5F5FF8AB4F5FF88B3F5FF87B3F5FF87B3F5FF + 87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF + 87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF + 87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF + 87B3F5FF89B4F5FF8BB5F5FF7EACF2FF4D88E1FF1F65CEFF1A5FC5FF1A5CBEFF + 1957B4FF134EA6FF2F61ABBB5D83BB68134B9CFF1751A7FF1856B1FF1858B8FF + 3B76CEFF7BA8EBFF86B1F1FF82AEF0FF81AEF1FF81ADF0FF80ADF0FF7FADF0FF + 7FACF0FF7EACF0FF7EABF0FF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF + 7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF + 7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABF0FF7FACF0FF7FADF0FF80ADF0FF + 80ADF0FF81AEF0FF82AEF1FF85B0F1FF82ADEFFF4A82D5FF1A5BBAFF1856B2FF + 1853A9FF0D469CFF597EB6A3A4B8D6272A5AA0FB0E4698FF1550A4FF1753ACFF + 225EB6FF2E68BFFF316BC3FF306BC5FF2E6BC5FF2B69C5FF2967C4FF2766C3FF + 2666C3FF2565C3FF2564C3FF2464C2FF2464C2FF2464C2FF2464C2FF2464C2FF + 2464C2FF2464C2FF2464C2FF2464C3FF2464C3FF2464C3FF2464C3FF2464C3FF + 2464C3FF2464C3FF2464C2FF2464C3FF2565C3FF2665C3FF2766C3FF2967C4FF + 2B68C4FF2D6AC5FF2F6BC5FF306CC4FF2E69C1FF2560B8FF1855AEFF1750A6FF + 11489CFF174B99FFA2B5D250C8D3E4007495C56522549DF5134998FF154C9EFF + 0C479FFF0A46A0FF0D49A4FF0E4BA6FF0F4DA8FF104DA9FF104EAAFF104EAAFF + 104EAAFF114EAAFF114EAAFF114FABFF114FACFF114FACFF114FACFF114FACFF + 114FACFF114FACFF114FACFF114FABFF114FABFF114FABFF114FABFF114FABFF + 114FABFF114FABFF114FABFF114EAAFF114EAAFF104EAAFF104EAAFF104EAAFF + 104DA9FF0F4DA9FF0E4BA6FF0D4AA5FF0B47A1FF09459EFF144C9EFF124898FF + 1A4D99FF6085BC8BB0C2DD00C1CEE0008AA6D10084A2CF2B6A8DC170295CA880 + 5279B3D0587FBAF04471B4F03D6CB2F0396AB2F03768B2F03768B1F03667B1F0 + 3567B1F03567B1F03567B1F03466B1F03466B1F03466B1F03466B1F03466B1F0 + 3466B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466B1F0 + 3466B1F03466B1F03466B1F03566B1F03567B1F03567B1F03567B1F03667B1F0 + 3768B1F03969B1F03C6BB2F04270B4F0527BB8F06487BCE92659A5863F6DAF78 + 7092C64B88A5D000A7BCDA00C000000000030000800000000001000080000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000800000000000000080000000 + 00010000E0000000000700002800000020000000400000000100200000000000 + 801000000000000000000000000000000000000097A7BD026485B4703165B4B7 + 2965BDCC2D69C1ED2E6AC5F02E6CC8F02D6CC9F02D6CCAF02D6CCAF02D6CCAF0 + 2D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF0 + 2D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CC9F02E6CC8F02E6BC6F0 + 2E69C2EF2B66BDD22F65B5BD6282B382AEB4BE0D6586B7591956B1FF155AC1FF + 1962CDFF1A65D5FF1B68D9FF1B69DCFF1B69DDFF1B6ADEFF1B6ADEFF1B6ADEFF + 1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF + 1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B69DCFF1B68DAFF + 1B65D6FF1962CFFF165BC3FF1253B2FF6686B77D4371B5AC1255BAFF1C63CEFF + 1D69DAFF1F6DE2FF1F6FE6FF2070E9FF2071EAFF2071EAFF2071EBFF2071EBFF + 2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF + 2071EBFF2071EBFF2071EBFF2071EBFF2071EAFF2071EAFF2070E9FF1F70E7FF + 1F6EE3FF1D6ADBFF1D64D0FF1458BDFF3366B3C03467B4D6165BC3FF1D67D6FF + 1F6DE2FF2072EBFF2173EFFF2175F2FF2175F2FF2175F3FF2175F3FF2175F3FF + 2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF + 2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F2FF2174F2FF2173F0FF + 2072EBFF1F6EE4FF1E68D8FF185EC6FF2860B5D4376BB9F4175DC7FF1E69DAFF + 1F70E7FF2173EFFF2176F3FF2177F6FF2077F7FF1C74F7FF1771F7FF1570F7FF + 1872F7FF1E75F7FF2278F7FF2278F7FF2278F8FF2278F8FF2278F8FF2278F7FF + 2177F7FF1D75F7FF1872F7FF1771F7FF1A73F7FF1E76F7FF2177F6FF2176F4FF + 2174F0FF1F70E8FF1E6ADCFF1860CBFF2F66BAEF3067B9F0185FCAFF1E6ADDFF + 2071E9FF2175F2FF2177F6FF1D75F8FF1972F8FF3785FAFF609EFAFF70A8FBFF + 5D9CFAFF3081FAFF1772FAFF2178FAFF2279FAFF2279FAFF2279FAFF1F77FAFF + 1872FAFF3483FAFF599AFAFF609EFAFF4B91FAFF267AF9FF1872F8FF2076F7FF + 2175F3FF2071EBFF1F6CDFFF1961CDFF2C65BBF02F66BAF01860CBFF1E6BDEFF + 2071EAFF2175F3FF1B73F7FF277BF9FF93BDFCFFEDF4FEFFFFFFFEFFFFFFFEFF + FFFFFEFFDFECFDFF6DA6FCFF1B75FAFF1F77FAFF237AFAFF1D76FAFF2279FAFF + 83B4FCFFE8F0FEFFFEFEFEFFFFFFFEFFF9FBFEFFD1E2FDFF5F9DFAFF1972F7FF + 1F74F3FF2072ECFF1F6CE0FF1A62CFFF2A65BBF02E66BAF01860CBFF1E6BDEFF + 2071EBFF1C72F3FF2D7EF7FFC4DAFCFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FFFFFEFFFFFFFEFFFFFFFEFF95BEFCFF1C75FAFF1C76FAFF287CFAFFB8D3FDFF + FFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFDFEFDFF86B4FAFF + 1971F3FF1F71ECFF1F6CE0FF1A62CFFF2A65BCF02E67BBF01860CBFF1E6BDEFF + 1F71EBFF1B71F3FFACCBF9FFFFFFFDFFFFFEFEFFFEFEFEFFFFFFFEFFFFFFFEFF + FFFFFEFFFEFEFEFFFFFFFEFFFFFFFEFF74ABFBFF106EFAFFA3C7FCFFFFFFFEFF + FFFFFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFFFFFDFFFFFEFCFF + 67A0F5FF166CECFF1F6CE0FF1A62CFFF2A64BCF02E67BBF01960CBFF1E6BDEFF + 186CEBFF4F91F3FFFBFBFBFFFFFFFDFFFEFEFEFFFFFFFEFFE5EFFDFFBDD6FDFF + EEF4FEFFFFFFFEFFFEFEFEFFFFFFFFFFD8E8FEFF5597FBFFF9FBFFFFFFFFFFFF + FEFEFEFFFFFFFEFFE9F1FDFFD5E5FDFFFEFEFEFFFFFFFEFFFDFDFEFFFFFFFCFF + D1E0F7FF2474EDFF1C6BE0FF1A62CFFF2A64BCF02D66BBF01960CBFF1E6BDEFF + 1168EBFF86B2F5FFFFFFFBFFFEFDFDFFFFFFFEFFDCEAFDFF3D89FBFF1772FAFF + 4B92FBFFE9F2FEFFFFFFFFFFFFFFFFFFF7FBFFFFCEE1FEFFFFFFFFFFFFFFFFFF + FFFFFFFFCCE0FDFF3685FBFF1E76FAFF76ABFCFFFAFCFEFFFFFFFEFFFEFDFCFF + F6F7F8FF4A8CEDFF1768E0FF1A62CFFF2A64BCF02D66BBF01960CCFF1E6BDFFF + 146AEBFF9ABEF5FFFFFFFBFFFEFEFEFFFFFFFEFF96BFFCFF106EFAFF1F77FAFF + 1671FAFFB0CFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF74ABFCFF106EFAFF1D76FAFF1E76FAFFCADFFDFFFFFFFEFFFDFDFCFF + FBFAF8FF629AEEFF1466E0FF1A62CFFF2A65BCF02D66BBF01960CCFF1E6BDFFF + 1369EBFF98BDF5FFFFFFFCFFFEFEFEFFFFFFFEFF9CC3FCFF1672FAFF2179FAFF + 1571FBFF90BCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF71A8FDFF1672FAFF2078FAFF2077FAFFBED8FDFFFFFFFEFFFEFDFCFF + F9F9F8FF5D97EEFF1566E1FF1A62CFFF2965BCF02D66BBF01960CCFF1E6BDFFF + 1169EBFF84B2F5FFFFFFFCFFFEFEFEFFFFFFFEFFC3DAFDFF1E76FAFF2178FAFF + 1D76FBFF3B88FCFFBAD5FEFFD9E8FEFFD9E8FEFFD9E8FEFFD9E8FEFFD9E8FEFF + D9E8FEFF63A0FDFF1873FBFF2078FAFF1F77FAFFBDD7FDFFFFFFFEFFFFFEFCFF + EFF3F8FF3C83EDFF1969E1FF1A62D0FF2965BCF02D66BBF01960CCFF1E6BDFFF + 166CEBFF5A98F5FFFEFDFCFFFFFFFEFFFFFFFEFFF7FAFEFF4C92FBFF1872FBFF + 237AFBFF1D76FBFF2078FBFF287DFBFF287DFBFF287DFBFF287DFBFF287DFBFF + 287DFBFF247AFBFF2279FBFF1B75FAFF257BFAFFD8E7FEFFFFFFFEFFFFFFFCFF + ABC9F6FF1A6FEDFF1D6CE1FF1A62D0FF2965BDF02D66BBF01960CCFF1E6BDFFF + 1C70EBFF2779F3FFD3E3FBFFFFFFFEFFFEFEFEFFFFFFFEFFC7DDFDFF257BFBFF + 1973FBFF2279FBFF2078FBFF1F77FBFF1F77FBFF1F77FBFF1F77FBFF1F77FBFF + 1F77FBFF2178FBFF1F77FBFF3283FAFFB4D1FDFFFFFFFEFFFEFFFEFFFFFFFCFF + 73A7F5FF126AEDFF1F6CE1FF1A62D0FF2965BDF02D66BBF01960CCFF1E6BDFFF + 1F71EBFF1870F3FFB0CEFAFFFFFFFEFFFEFEFEFFFEFEFEFFFFFFFEFFC6DCFEFF + 418CFCFF1873FBFF1571FBFF1873FBFF1873FBFF1873FBFF1873FBFF1973FBFF + 1E76FBFF2279FBFF1F77FBFF2C7FFAFFD5E5FDFFFFFFFEFFFEFEFEFFFFFFFCFF + B6D0F7FF1C70EDFF1D6CE1FF1A62D0FF2965BCF02D66BBF01960CCFF1E6BDFFF + 196EEBFF478CF3FFF3F6FBFFFFFFFEFFFEFEFEFFFFFFFEFFEAF2FDFFD0E2FDFF + B1D0FEFF89B7FDFF6FA7FDFF5F9EFCFF609FFCFF609FFCFF619FFCFF5E9DFCFF + 3584FCFF1D76FBFF2279FBFF1672FAFF63A0FBFFFEFEFEFFFFFFFEFFFFFFFCFF + EAEFF8FF337EEDFF1A6AE1FF1A62CFFF2965BCF02D66BBF01961CCFF1E6BDFFF + 1269EBFF82B0F5FFFFFFFBFFFEFEFEFFFFFFFEFFD7E7FDFF3A88FBFF1F77FAFF + 2078FAFFB6D3FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + DCEAFEFF3886FBFF1E77FAFF1F77FAFF2C7FFAFFE3EEFEFFFFFFFEFFFEFDFCFF + F7F7F8FF5291EDFF1668E0FF1A62CFFF2965BCF02D66BBF01961CBFF1E6BDEFF + 1369EBFF98BEF5FFFFFFFBFFFDFDFEFFFFFFFEFF96BFFCFF106EFAFF1F77FAFF + 1672FAFFA5C9FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFF69A4FBFF1772FAFF2078FAFF1E77FAFFBFD8FDFFFFFFFEFFFDFDFCFF + FBFAF8FF639BEEFF1466E0FF1A62CFFF2964BCF02D66BBF01960CBFF1E6BDEFF + 1269EBFF94BAF5FFFFFFFBFFFDFDFDFFFFFFFEFFADCDFDFF136FFAFF1370FAFF + 1B75FAFFC6DCFDFFFFFFFFFFFFFFFFFFFDFEFFFFF7FAFFFFFFFFFFFFFEFEFFFF + FFFFFEFF82B3FCFF0D6CFAFF1772FAFF267BFAFFD9E8FEFFFFFFFEFFFDFDFCFF + FBFAF8FF5F99EEFF1466E0FF1A62CFFF2A64BCF02D66BBF01860CBFF1E6BDEFF + 136AEBFF71A5F4FFFFFFFBFFFEFEFDFFFFFFFEFFFAFBFEFF91BCFCFF5498FBFF + A0C5FCFFFEFEFEFFFEFEFEFFFFFFFEFFECF4FEFFA1C6FDFFFFFFFEFFFEFEFEFF + FFFFFEFFE4EEFDFF64A1FBFF3F8AFBFFA6C9FCFFFFFFFEFFFEFEFDFFFFFEFCFF + F2F4F8FF4085EDFF1869E0FF1A62CFFF2A64BCF02D65BAF01860CBFF1E6BDEFF + 1B6FEBFF317FF3FFE4EDFBFFFFFFFDFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFF + FFFFFEFFFFFFFEFFFEFEFEFFFFFFFEFFB0CEFDFF3383FAFFEEF4FEFFFFFFFEFF + FEFEFEFFFFFFFEFFFFFFFEFFF8FAFEFFFFFFFEFFFFFEFEFFFDFDFDFFFFFFFCFF + BBD3F7FF1D70ECFF1D6CE0FF1A62CFFF2A64BCF02E66BAF01860CBFF1E6BDEFF + 2071EAFF166FF3FF6BA4F8FFFEFEFDFFFFFFFEFFFEFEFEFFFFFEFEFFFFFFFEFF + FFFEFEFFFFFFFEFFFFFFFEFFE6F0FEFF3F8BFBFF106EFAFF80B2FCFFFFFFFEFF + FFFFFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFEFEFFFEFEFEFFFFFFFDFFEEF3FBFF + 488DF4FF186DECFF1F6CE0FF1A62CEFF2A64BBF02E66B9F0185FC9FF1E6ADDFF + 2071EAFF2074F1FF1871F5FF72A8F9FFEAF2FCFFFFFFFDFFFFFFFDFFFFFFFDFF + FFFFFEFFFFFFFEFFD1E2FDFF4A91FBFF1873F9FF2178FAFF1C75FAFF8BB8FCFF + F9FBFEFFFFFFFEFFFFFFFDFFFFFFFDFFFFFFFDFFFFFFFDFFE1ECFCFF5798F8FF + 166EF2FF2071EBFF1F6CDFFF1A62CDFF2A64BAF02E65B8F0185EC8FF1E69DBFF + 1F70E8FF2174F0FF2075F4FF1671F6FF3D88F7FF83B2F9FFBDD5FAFFD1E2FBFF + B8D3FAFF74A9F9FF297DF8FF1972F7FF2278F8FF2278F8FF1F76F8FF1872F7FF + 5093F9FFABCBFAFFE7EFFBFFF1F5FBFFD2E2FBFF8BB8F9FF3683F6FF1770F4FF + 2174F1FF1F70E9FF1E6ADDFF1960CBFF2A64B9F02F65B5F0175DC4FF1E68D7FF + 1F6EE3FF2072ECFF2074F0FF1F74F2FF1970F3FF116CF3FF1C72F3FF2276F3FF + 1A71F3FF116CF3FF1A71F4FF1E74F4FF1D74F4FF1D74F4FF1D74F4FF1D73F4FF + 146EF3FF176FF3FF297AF3FF2E7DF3FF2276F3FF136DF3FF1A71F2FF2173F1FF + 2072EDFF1F6FE5FF1E69D9FF185FC7FF2B63B6F13769B3F11558BDFF1C64D0FF + 1D6ADCFF1C6CE3FF1B6DE7FF2474EBFF2C7AEEFF317DEFFF317EEFFF327EF0FF + 337FF0FF3580F0FF3580F0FF3580F0FF3580F0FF3580F0FF3580F0FF3580F0FF + 3580F0FF347FF0FF317EF0FF307CEFFF2F7CEFFF2D7AEEFF2575ECFF1C6EE8FF + 1C6DE4FF1E6BDDFF1D65D2FF175BC0FF2D63B2EE2B5FAACF1554B3FF1B5FC5FF + 1A63D0FF2A71DCFF5793EBFF76A8F3FF80AFF6FF83B1F6FF84B1F6FF84B2F6FF + 84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF + 84B2F6FF84B2F6FF84B2F6FF84B1F6FF83B1F6FF81AFF6FF78AAF4FF5D97EDFF + 2F75DEFF1A63D1FF1B60C7FF1857B6FF1F57A8CE3A68ADA6104BA3FF1656B4FF + 2A69C8FF6E9FE8FF87B1F2FF83AFF1FF81ADF1FF80ADF0FF7FACF0FF7FACF0FF + 7FACF0FF7FACF0FF7EACF0FF7FACF0FF7FACF0FF7FACF0FF7FACF0FF7EACF0FF + 7FACF0FF7FACF0FF7FACF0FF7FACF0FF80ADF0FF81ADF1FF83AFF1FF87B2F2FF + 76A5EBFF3270CCFF1656B6FF104BA5FF3A68ACC28EA7CC50184C9AFF0E489EFF + 1A55ADFF2661B9FF2763BDFF2663BDFF2361BDFF2361BEFF2260BDFF2160BDFF + 2160BDFF2160BDFF2160BDFF2160BDFF2160BDFF205FBDFF205FBCFF205FBCFF + 205FBDFF205FBDFF205FBDFF205FBDFF2160BDFF2462BEFF2664BEFF2964BDFF + 2963BAFF1D58AFFF104AA1FF0F4698FF819CC66FB7C7DE006E90C3523B69ADAC + 2B5BA5D43363ABF0275CAAF0255BAAF0245AABF01C54A7F01C54A7F01C54A7F0 + 1C54A7F01C54A7F01C54A7F01C54A7F01C54A8F0235AABF0235AABF0235AABF0 + 235AABF0235AABF0245AABF0245AABF0245AABF01C54A7F01D54A6F01D53A4F0 + 2156A4F02154A1E63362A9C25D83BC5F9EB5D701000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000080000000} + end + object lHeader: TLabel + Left = 73 + Top = 13 + Width = 217 + Height = 23 + Caption = 'XAMPP Control Panel v' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -19 + Font.Name = 'Verdana' + Font.Style = [] + ParentFont = False + end + object reLog: TRichEdit + Left = 8 + Top = 219 + Width = 690 + Height = 163 + Anchors = [akLeft, akTop, akRight, akBottom] + Font.Charset = ANSI_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [] + HideSelection = False + ParentFont = False + ScrollBars = ssBoth + TabOrder = 8 + WordWrap = False + end + object bConfig: TBitBtn + Left = 611 + Top = 8 + Width = 90 + Height = 23 + Caption = 'Config' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 0 + OnClick = bConfigClick + end + object bSCM: TBitBtn + Left = 611 + Top = 133 + Width = 90 + Height = 23 + Caption = 'Win-Services' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 1 + OnClick = bSCMClick + end + object gbModules: TGroupBox + Left = 8 + Top = 44 + Width = 597 + Height = 169 + Caption = ' Modules ' + TabOrder = 2 + object lPIDs: TLabel + Left = 143 + Top = 14 + Width = 42 + Height = 13 + Alignment = taRightJustify + Caption = 'PID(s)' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [fsBold] + ParentFont = False + end + object lPorts: TLabel + Left = 261 + Top = 14 + Width = 46 + Height = 13 + Alignment = taRightJustify + Caption = 'Port(s)' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [fsBold] + ParentFont = False + end + object lApachePIDs: TLabel + Left = 128 + Top = 30 + Width = 57 + Height = 25 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + WordWrap = True + end + object lApachePorts: TLabel + Left = 250 + Top = 31 + Width = 57 + Height = 26 + Alignment = taRightJustify + AutoSize = False + Color = clBtnFace + ParentColor = False + Layout = tlCenter + end + object lMySQLPIDs: TLabel + Left = 122 + Top = 64 + Width = 63 + Height = 13 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + end + object lMySQLPorts: TLabel + Left = 244 + Top = 64 + Width = 63 + Height = 13 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + end + object lFileZillaPorts: TLabel + Left = 244 + Top = 91 + Width = 63 + Height = 13 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + end + object lFileZillaPIDs: TLabel + Left = 122 + Top = 91 + Width = 63 + Height = 13 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + end + object lMercuryPorts: TLabel + Left = 191 + Top = 110 + Width = 116 + Height = 29 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + WordWrap = True + end + object lMercuryPIDs: TLabel + Left = 122 + Top = 118 + Width = 63 + Height = 13 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + end + object Label1: TLabel + Left = 12 + Top = 14 + Width = 49 + Height = 13 + Alignment = taCenter + Caption = 'Service' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [fsBold] + ParentFont = False + end + object Label2: TLabel + Left = 64 + Top = 14 + Width = 65 + Height = 13 + Alignment = taCenter + AutoSize = False + Caption = 'Module' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [fsBold] + ParentFont = False + end + object Label3: TLabel + Left = 332 + Top = 14 + Width = 258 + Height = 13 + AutoSize = False + Caption = 'Action' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Verdana' + Font.Style = [fsBold] + ParentFont = False + end + object lTomcatPorts: TLabel + Left = 191 + Top = 137 + Width = 116 + Height = 29 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + WordWrap = True + end + object lTomcatPIDs: TLabel + Left = 122 + Top = 145 + Width = 63 + Height = 13 + Alignment = taRightJustify + AutoSize = False + Layout = tlCenter + end + object bApacheAction: TBitBtn + Left = 332 + Top = 34 + Width = 60 + Height = 21 + Caption = 'Start' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 0 + OnClick = bApacheActionClick + end + object bApacheAdmin: TBitBtn + Left = 398 + Top = 34 + Width = 60 + Height = 21 + Caption = 'Admin' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 1 + OnClick = bApacheAdminClick + end + object bMySQLAction: TBitBtn + Left = 332 + Top = 61 + Width = 60 + Height = 21 + Caption = 'Start' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 2 + OnClick = bMySQLActionClick + end + object bMySQLAdmin: TBitBtn + Left = 398 + Top = 61 + Width = 60 + Height = 21 + Caption = 'Admin' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 3 + OnClick = bMySQLAdminClick + end + object bFileZillaAction: TBitBtn + Left = 332 + Top = 88 + Width = 60 + Height = 21 + Caption = 'Start' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 4 + OnClick = bFileZillaActionClick + end + object bFileZillaAdmin: TBitBtn + Left = 398 + Top = 88 + Width = 60 + Height = 21 + Caption = 'Admin' + DoubleBuffered = True + Enabled = False + ParentDoubleBuffered = False + TabOrder = 5 + OnClick = bFileZillaAdminClick + end + object bMercuryAction: TBitBtn + Left = 332 + Top = 115 + Width = 60 + Height = 21 + Caption = 'Start' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 6 + OnClick = bMercuryActionClick + end + object bMercuryAdmin: TBitBtn + Left = 398 + Top = 115 + Width = 60 + Height = 21 + Caption = 'Admin' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 7 + OnClick = bMercuryAdminClick + end + object pApacheStatus: TPanel + Left = 64 + Top = 35 + Width = 65 + Height = 17 + BevelOuter = bvNone + Caption = 'Apache' + ParentBackground = False + TabOrder = 8 + end + object bApacheConfig: TBitBtn + Left = 464 + Top = 34 + Width = 60 + Height = 21 + Caption = 'Config' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 9 + OnClick = bApacheConfigClick + end + object bApacheLogs: TBitBtn + Left = 530 + Top = 34 + Width = 60 + Height = 21 + Caption = 'Logs' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 10 + OnClick = bApacheLogsClick + end + object bMySQLConfig: TBitBtn + Left = 464 + Top = 61 + Width = 60 + Height = 21 + Caption = 'Config' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 11 + OnClick = bMySQLConfigClick + end + object bMySQLLogs: TBitBtn + Left = 530 + Top = 61 + Width = 60 + Height = 21 + Caption = 'Logs' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 12 + OnClick = bMySQLLogsClick + end + object bFileZillaConfig: TBitBtn + Left = 464 + Top = 88 + Width = 60 + Height = 21 + Caption = 'Config' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 13 + OnClick = bFileZillaConfigClick + end + object bFileZillaLogs: TBitBtn + Left = 530 + Top = 88 + Width = 60 + Height = 21 + Caption = 'Logs' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 14 + OnClick = bFileZillaLogsClick + end + object bMercuryConfig: TBitBtn + Left = 464 + Top = 115 + Width = 60 + Height = 21 + Caption = 'Config' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 15 + OnClick = bMercuryConfigClick + end + object pMySQLStatus: TPanel + Left = 64 + Top = 62 + Width = 65 + Height = 17 + BevelOuter = bvNone + Caption = 'MySQL' + ParentBackground = False + TabOrder = 16 + end + object pFileZillaStatus: TPanel + Left = 64 + Top = 89 + Width = 65 + Height = 17 + BevelOuter = bvNone + Caption = 'FileZilla' + ParentBackground = False + TabOrder = 17 + end + object pMercuryStatus: TPanel + Left = 64 + Top = 116 + Width = 65 + Height = 17 + BevelOuter = bvNone + Caption = 'Mercury' + ParentBackground = False + TabOrder = 18 + end + object bMySQLService: TBitBtn + Left = 12 + Top = 61 + Width = 21 + Height = 21 + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 19 + OnClick = bMySQLServiceClick + end + object bFileZillaService: TBitBtn + Left = 12 + Top = 88 + Width = 21 + Height = 21 + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 20 + OnClick = bFileZillaServiceClick + end + object bApacheService: TBitBtn + Left = 12 + Top = 33 + Width = 21 + Height = 21 + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 21 + OnClick = bApacheServiceClick + end + object bMercurylogs: TBitBtn + Left = 530 + Top = 115 + Width = 60 + Height = 21 + Caption = 'Logs' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 22 + OnClick = bMercurylogsClick + end + object bTomcatAction: TBitBtn + Left = 332 + Top = 142 + Width = 60 + Height = 21 + Caption = 'Start' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 23 + OnClick = bTomcatActionClick + end + object bTomcatAdmin: TBitBtn + Left = 398 + Top = 142 + Width = 60 + Height = 21 + Caption = 'Admin' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 24 + OnClick = bTomcatAdminClick + end + object bTomcatConfig: TBitBtn + Left = 464 + Top = 142 + Width = 60 + Height = 21 + Caption = 'Config' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 25 + OnClick = bTomcatConfigClick + end + object pTomcatStatus: TPanel + Left = 64 + Top = 143 + Width = 65 + Height = 17 + BevelOuter = bvNone + Caption = 'Tomcat' + ParentBackground = False + TabOrder = 26 + end + object bTomcatLogs: TBitBtn + Left = 530 + Top = 142 + Width = 60 + Height = 21 + Caption = 'Logs' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 27 + OnClick = bTomcatLogsClick + end + object bTomcatService: TBitBtn + Left = 12 + Top = 142 + Width = 21 + Height = 21 + DoubleBuffered = True + Enabled = False + ParentDoubleBuffered = False + TabOrder = 28 + OnClick = bFileZillaServiceClick + end + object bMercuryService: TBitBtn + Left = 12 + Top = 115 + Width = 21 + Height = 21 + DoubleBuffered = True + Enabled = False + ParentDoubleBuffered = False + TabOrder = 29 + OnClick = bFileZillaServiceClick + end + end + object sbMain: TStatusBar + Left = 0 + Top = 388 + Width = 706 + Height = 19 + Panels = <> + end + object bQuit: TBitBtn + Left = 611 + Top = 191 + Width = 90 + Height = 23 + Caption = 'Quit' + DoubleBuffered = True + Glyph.Data = { + DE010000424DDE01000000000000760000002800000024000000120000000100 + 0400000000006801000000000000000000001000000000000000000000000000 + 80000080000000808000800000008000800080800000C0C0C000808080000000 + FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00388888888877 + F7F787F8888888888333333F00004444400888FFF444448888888888F333FF8F + 000033334D5007FFF4333388888888883338888F0000333345D50FFFF4333333 + 338F888F3338F33F000033334D5D0FFFF43333333388788F3338F33F00003333 + 45D50FEFE4333333338F878F3338F33F000033334D5D0FFFF43333333388788F + 3338F33F0000333345D50FEFE4333333338F878F3338F33F000033334D5D0FFF + F43333333388788F3338F33F0000333345D50FEFE4333333338F878F3338F33F + 000033334D5D0EFEF43333333388788F3338F33F0000333345D50FEFE4333333 + 338F878F3338F33F000033334D5D0EFEF43333333388788F3338F33F00003333 + 4444444444333333338F8F8FFFF8F33F00003333333333333333333333888888 + 8888333F00003333330000003333333333333FFFFFF3333F00003333330AAAA0 + 333333333333888888F3333F00003333330000003333333333338FFFF8F3333F + 0000} + NumGlyphs = 2 + ParentDoubleBuffered = False + TabOrder = 3 + OnClick = bQuitClick + end + object bHelp: TBitBtn + Left = 611 + Top = 162 + Width = 90 + Height = 23 + Caption = 'Help' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 4 + OnClick = bHelpClick + end + object bExplorer: TBitBtn + Left = 611 + Top = 104 + Width = 90 + Height = 23 + Caption = 'Explorer' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 5 + OnClick = bExplorerClick + end + object bNetstat: TBitBtn + Left = 611 + Top = 37 + Width = 90 + Height = 23 + Caption = 'Netstat' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 6 + OnClick = bNetstatClick + end + object bXamppShell: TBitBtn + Left = 611 + Top = 66 + Width = 90 + Height = 23 + Caption = 'xampp-Shell' + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 9 + OnClick = bXamppShellClick + end + object TimerUpdateStatus: TTimer + Enabled = False + Interval = 500 + OnTimer = TimerUpdateStatusTimer + Left = 44 + Top = 224 + end + object TrayIcon1: TTrayIcon + Icon.Data = { + 000001000300404000000100200028420000360000003030000001002000A825 + 00005E4200002020000001002000A81000000668000028000000400000008000 + 00000100200000000000004200000000000000000000000000000000000094A5 + BD0094A5BD009FAAB903758FB5484370B283416FB5B4366BB9B82561BBBB366B + BAEC396EBDF0386EC0F0376EC1F0376FC3F0366FC3F0366FC4F0366FC4F0366F + C5F0366FC5F0366FC5F03670C5F03670C5F03670C5F03670C5F03670C5F03670 + C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670 + C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670C5F03670 + C5F03670C5F03670C5F03670C5F03670C5F03670C5F0366FC5F0366FC5F0366F + C4F0366FC4F0366FC3F0376FC3F0376EC2F0376EC0F0386EBEF03A6DBAF03369 + BAD43068BAB84371B5B84973B29C6283B5609BA7B80AAEB3BD00B0B6BF0094A5 + BD0097A7BE0A6082B3BD255CAEFF1957B4FF1356BAFF165ABFFF195DC4FF175E + C7FF175FCAFF1861CDFF1861CFFF1862D0FF1962D0FF1963D1FF1863D2FF1863 + D2FF1863D2FF1863D2FF1863D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963 + D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963 + D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963D2FF1963 + D2FF1963D2FF1963D2FF1963D2FF1863D2FF1863D2FF1863D2FF1863D2FF1863 + D2FF1863D1FF1962D1FF1862D0FF1862CFFF1860CDFF1760CBFF165EC9FF175D + C5FF175BC1FF1356BBFF1555B5FF1E58AFFF4D74B0DDA6AFBD43B2B7BF007F97 + BD005B7FB5961450ABFF1454B3FF195BBCFF1B5FC3FF1B61C8FF1C63CDFF1C64 + D0FF1D66D3FF1D68D6FF1D68D8FF1D68D9FF1E69DAFF1E69DAFF1E69DAFF1E69 + DBFF1E69DBFF1E69DBFF1E69DBFF1E69DBFF1E69DAFF1E69DAFF1E69DAFF1E69 + DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69 + DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69DAFF1E69 + DAFF1E69DAFF1E69DAFF1E69DBFF1E69DBFF1E69DBFF1E69DBFF1E69DBFF1E69 + DAFF1E69DAFF1E69DAFF1D68D9FF1D68D8FF1D67D6FF1D66D4FF1C65D2FF1C63 + CEFF1C61CAFF1B5FC5FF195CBDFF1757B6FF0D4DACFF4C75B1D99BA9C0076F8D + BB0B2C61AEE61352B0FF1A5BBBFF1B5EC3FF1C62C9FF1C64D0FF1D67D5FF1E68 + D8FF1E6ADBFF1E6BDDFF1E6CDFFF1F6CE0FF1F6DE1FF1F6DE1FF1F6DE1FF1F6D + E1FF1F6DE1FF1F6DE1FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6E + E2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6E + E2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6E + E2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6EE2FF1F6DE2FF1F6DE1FF1F6DE1FF1F6D + E1FF1F6DE1FF1F6DE1FF1F6DE0FF1E6CDFFF1E6BDEFF1E6ADCFF1E69D9FF1D67 + D6FF1D65D1FF1C63CBFF1B5FC4FF1A5CBDFF1655B4FF1D57ACFF7692BC5E839B + BF611F59AFFD1757B6FF1A5DC0FF1B61C8FF1C64D0FF1D67D7FF1E6ADBFF1E6B + DEFF1F6DE1FF1F6EE3FF1F6FE5FF1F70E6FF1F6FE7FF2070E7FF2070E8FF2070 + E8FF2070E8FF2071E8FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070 + E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070 + E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070E9FF2070 + E9FF2070E9FF2070E9FF2070E9FF2070E9FF2071E8FF2071E8FF2070E8FF2070 + E8FF2070E7FF1F70E7FF1F6FE7FF1F70E5FF1F6FE4FF1F6EE1FF1F6CDFFF1E6A + DCFF1D68D8FF1D65D2FF1C62CBFF1A5EC2FF195AB9FF1251AEFF5179B5A14B76 + B7831C57B1FF1859BAFF1B5FC5FF1C63CDFF1D67D5FF1E6ADBFF1F6CDFFF1F6D + E3FF1F6FE6FF2070E8FF2071EAFF2072EBFF2072ECFF2072EDFF2072EDFF2072 + EDFF2072EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073 + EDFF2073EDFF2073EDFF2073EDFF2073EEFF2073EEFF2073EEFF2073EEFF2073 + EEFF2073EEFF2073EEFF2073EEFF2073EDFF2073EDFF2073EDFF2073EDFF2073 + EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2073EDFF2072EDFF2072 + EDFF2072EDFF2072ECFF2072ECFF2071EAFF2070E9FF1F70E7FF1F6EE4FF1F6D + E0FF1E6ADCFF1D68D7FF1C64D0FF1B5FC7FF1A5BBDFF1252B2FF3E6EB3BC406E + B3871755B2FF1A5BBEFF1B60C8FF1C64D1FF1E68D8FF1E6CDEFF1F6EE3FF206F + E7FF2071EAFF2072ECFF2072EEFF2173EFFF2174EFFF2174F0FF2174F0FF2174 + F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174 + F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174 + F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174 + F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174F1FF2174 + F0FF2174F0FF2173F0FF2173EFFF2173EEFF2072EDFF2071EAFF2070E8FF1F6E + E4FF1F6CDFFF1E69DAFF1D66D3FF1C61CAFF1A5DC1FF1656B6FF265EB0B66284 + B9E90F51B4FF1A5DC1FF1C62CBFF1D66D4FF1E6ADBFF1F6DE1FF1F70E6FF2071 + EAFF2073EDFF2173F0FF2174F1FF2175F2FF2175F3FF2175F4FF2175F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2175 + F4FF2175F4FF2175F3FF2175F2FF2175F2FF2174F0FF2073EEFF2071EBFF2070 + E7FF1F6EE3FF1E6BDDFF1D67D6FF1C63CDFF1B5FC4FF1657B8FF3063B2C8577D + B9F21052B6FF1B5EC3FF1C63CDFF1D67D6FF1E6BDEFF1F6EE3FF2071E9FF2072 + ECFF2174F0FF2175F2FF2176F3FF2176F5FF2177F5FF2177F6FF2177F6FF2177 + F6FF2177F6FF2177F6FF2177F6FF2177F7FF2177F7FF2177F7FF2177F7FF2177 + F7FF2177F7FF2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177 + F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177F7FF2177F7FF2177 + F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F6FF2177F6FF2177F6FF2177 + F6FF2177F6FF2177F5FF2176F5FF2176F4FF2175F3FF2174F0FF2073EDFF2071 + EAFF1F6FE5FF1F6CDFFF1E69D9FF1C65D0FF1B60C6FF1456BAFF3E6FB6F04E79 + B9F01254B7FF1B5FC5FF1C64CFFF1D68D8FF1E6CDFFF1F6FE5FF2071EAFF2173 + EEFF2175F1FF2176F3FF2177F5FF2177F6FF2177F7FF2177F7FF2177F7FF2278 + F7FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278 + F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278 + F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278 + F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F8FF2278F7FF2177 + F7FF2177F7FF2177F7FF2177F7FF2177F5FF2176F4FF2175F2FF2173EFFF2072 + EBFF1F70E7FF1F6DE1FF1E69DBFF1C65D2FF1B60C8FF1558BCFF376BB7F04876 + B9F01355B9FF1B5FC6FF1C64D1FF1E69DAFF1F6DE0FF1F70E7FF2072EBFF2173 + EFFF2175F2FF2176F5FF2177F6FF2177F7FF2278F8FF2278F8FF2278F8FF1F76 + F9FF1A73F8FF1671F8FF1470F9FF136FF9FF136FF9FF1570F9FF1872F9FF1C74 + F9FF2177F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278 + F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2077 + F9FF1C74F9FF1872F9FF1671F9FF1570F9FF1670F8FF1772F8FF1B74F8FF1F76 + F8FF2278F8FF2278F8FF2177F7FF2177F6FF2177F5FF2175F3FF2174F0FF2072 + EDFF2070E9FF1F6EE2FF1E6ADBFF1D66D3FF1B61C9FF1659BDFF3669B8F04674 + B9F01356BAFF1B60C7FF1C65D1FF1E69DAFF1F6DE1FF1F70E7FF2072ECFF2173 + F0FF2175F2FF2177F5FF2177F7FF2278F8FF2278F9FF2077F9FF1973F9FF287C + F9FF4F93FAFF71A8FBFF84B4FBFF90BBFBFF8EBAFCFF7EB0FBFF66A2FBFF448D + FAFF1E76F9FF1B74F9FF2278F9FF2279FAFF2279FAFF2279FAFF2279FAFF2279 + FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2178F9FF1772F9FF2077 + F9FF448DFAFF65A1FBFF75ABFBFF79ACFBFF78ACFBFF69A3FBFF4D92FAFF2C7E + F9FF1973F9FF1F76F9FF2278F8FF2177F7FF2177F5FF2176F4FF2174F1FF2073 + EDFF2070E9FF1F6EE3FF1E6ADCFF1D66D4FF1C61CAFF1659BEFF3569B8F04473 + B9F01356BAFF1B60C7FF1C65D2FF1E69DBFF1F6DE1FF2070E8FF2072EDFF2174 + F0FF2175F4FF2177F6FF2177F7FF2278F8FF1B73F9FF1C75F9FF6AA4FBFFDAE8 + FCFFFAFCFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFF8FB + FEFFB6D2FDFF3E8AFBFF1772FAFF1F77FAFF2279FAFF2279FAFF2279FAFF2279 + FAFF2279FAFF2279FAFF2279FAFF2279FAFF1B75FAFF1C75FAFF5C9CFBFFC0D8 + FDFFF8FAFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFAFCFDFFE1EC + FCFF7DAFFBFF2479F9FF1973F8FF2177F7FF2177F7FF2176F4FF2174F1FF2073 + EEFF2071E9FF1F6EE3FF1E6ADCFF1D66D4FF1C61CAFF1659BEFF3469B8F04373 + B9F01356BBFF1B60C8FF1C65D2FF1E69DBFF1F6DE1FF2070E8FF2072EDFF2174 + F1FF2176F4FF2177F6FF2177F7FF1772F8FF438BFAFFBFD8FCFFFFFFFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFEFEFFFFFFFEFFFFFF + FEFFFFFFFEFFECF3FEFF8BB8FCFF1F77FAFF1D76FAFF2279FAFF2279FAFF2279 + FAFF2279FAFF2279FAFF2279FAFF1572FAFF3F8AFBFFB9D4FDFFFEFFFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFF + FEFFFFFFFEFFCFE1FCFF599AFAFF1671F7FF1F76F7FF2176F5FF2175F1FF2073 + EEFF2071EAFF1F6EE3FF1E6BDDFF1D66D5FF1C61CAFF1659BFFF3469B8F04273 + B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF2073EDFF2174 + F1FF2176F4FF2177F6FF1671F7FF5999FAFFE7F0FDFFFFFFFEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFB4D1FDFF287CFAFF1D76FAFF2279FAFF2279 + FAFF2279FAFF2279FAFF1672FAFF5F9DFBFFE4EEFEFFFFFFFEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFFFFFDFFF7FAFDFF80B0FAFF1871F6FF1F75F5FF2175F2FF2173 + EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CBFF1659BFFF3368B9F04272 + B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF2073EDFF2174 + F1FF2176F4FF1670F6FF5395F8FFF0F5FDFFFFFFFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFBAD5FDFF2279FAFF1F77FAFF2279 + FAFF2279FAFF1672FAFF5B9BFCFFF2F6FEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFDFDFDFFFFFFFDFFFFFFFCFF7FB0F8FF1770F4FF2074F2FF2173 + EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CBFF165ABFFF3368B9F04272 + B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF2073EDFF2174 + F1FF1B72F4FF3584F7FFDFEAFBFFFFFFFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFF9DC3FCFF1873FAFF2279 + FAFF1C76FAFF3887FBFFE6EFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFFFEFCFFFBFCFBFF5E9BF6FF156EF2FF2173 + EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3368B9F04272 + B9F01457BBFF1B60C8FF1C65D2FF1E6ADAFF1F6EE2FF2070E9FF2073EDFF2074 + F1FF1A71F4FFA1C4F9FFFFFFFCFFFEFEFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFF9FBFEFF5B9CFCFF1873 + FAFF1B75FAFFB5D1FDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFFFFFBFFDBE7F9FF327FF3FF1C71 + EFFF2071EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3368B9F04272 + B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF2073EDFF1A70 + F1FF488DF5FFF7F8FAFFFFFEFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFBFD9FEFF1470 + FBFF5497FCFFFAFCFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFFFFFF9FF78AAF4FF136B + EFFF2071EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3368B9F04171 + B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF2073EDFF146D + F1FF78AAF6FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFCFDFEFFC1DAFDFF9FC5FCFFA9CAFCFFDEEAFDFFFFFFFEFFFFFE + FEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDF4FFFF2C7F + FCFFA1C6FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFFFFFEFFD4E5FDFFBDD7FDFFCDE0FDFFFBFCFEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFFFFEF9FFC8DAF5FF2374 + EFFF1D70EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3268B9F04171 + B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1F72EEFF1D72 + F1FFB5D0F7FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFF7FAFEFF6FA7FCFF1A74FAFF1772FAFF1873FAFF2B7EFAFFB2D0FDFFFFFF + FEFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FB1 + FDFFC6DDFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFF + FEFFF6F9FEFF7DB0FCFF237AFAFF1F77FAFF1F77FAFF5C9CFCFFE2EDFEFFFFFF + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFFBFAF9FFFBFAF6FF619B + F0FF166BEAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3268B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1C70EEFF2F7D + F2FFE8EEF8FFFFFEFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FEFF8CB9FCFF1370FAFF1F78FAFF2179FAFF2179FAFF1C75FAFF1E77FAFFB7D3 + FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCADF + FEFFEBF3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFE + FEFF80B2FCFF126FFAFF1E77FAFF2078FAFF2078FAFF1270FAFF5B9BFBFFFCFD + FEFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FFFFFFF6FF95BA + F1FF1469EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3268B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1B6FEEFF3C84 + F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFF9FA + FEFF458EFBFF1B74FAFF2279FAFF2279FAFF2279FAFF2279FAFF1471FAFF64A2 + FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDAE9 + FEFF257BFAFF1F77FAFF2279FAFF2279FAFF2279FAFF2178FAFF1B75FAFFB8D4 + FDFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFDF6FFA6C4 + F1FF196DEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3268B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE2FF2070E9FF1A6FEEFF458A + F2FFF2F4F8FFFDFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFF1F6 + FEFF3082FAFF1E77FAFF2279FAFF2279FAFF2279FAFF2279FAFF1A74FBFF5498 + FCFFFDFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0D8 + FEFF2077FAFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF126FFAFF8DBA + FCFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFCF6FFAEC9 + F2FF1C6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2070E9FF1A6FEEFF3F86 + F2FFF0F4F8FFFDFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFF4F8 + FEFF3D8AFBFF1D76FAFF2279FAFF2279FAFF2279FAFF2279FBFF1C75FBFF448E + FCFFFAFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1D9 + FEFF2178FBFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF1370FAFF90BC + FCFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFCF6FFA9C6 + F2FF1B6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2070E9FF1B6FEEFF3982 + F2FFF0F4F8FFFEFCFAFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFAFB + FEFF4D93FBFF1B75FAFF2279FAFF2279FAFF2279FBFF2279FBFF2078FBFF2279 + FBFFCCE0FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1D9 + FEFF2178FBFF2078FBFF2279FAFF2279FAFF2279FAFF2279FAFF1370FAFF90BC + FCFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFEF6FF9CBE + F1FF156BEAFF1F6EE5FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF1C71EEFF2C7B + F1FFE2EBF8FFFFFEFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FEFF68A3FCFF1672FAFF2279FAFF2279FAFF2279FBFF2279FBFF2279FBFF1873 + FBFF5B9BFCFFF8FBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCADE + FEFF2178FBFF2078FBFF2279FBFF2279FAFF2279FAFF2279FAFF1370FAFF90BC + FCFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFBFBFBFFFAF9F9FFFFFDF6FF77A7 + F1FF136AEAFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF1F72EEFF1D72 + F1FFB4D0F8FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFEFEFFFFFF + FEFF9CC3FCFF1672FAFF2279FAFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF1C75FBFF4D93FCFF8EBBFDFFA8CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CB + FDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFA9CBFDFFAACBFDFF83B4 + FDFF2279FBFF2179FBFF2279FBFF2279FAFF2279FAFF2279FAFF1270FAFF8EBB + FCFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFBFBFBFFFEFDF9FFDBE5F6FF307D + F0FF1C6FEBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF146C + F1FF82B1F6FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFEFF4FEFF3B88FBFF1B75FAFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF1A74FBFF1571FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74 + FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1A74FBFF1C75 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FAFF2279FAFF1571FAFF9EC4 + FCFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFCFBFBFFFFFFF9FF83B0F5FF136B + EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF186F + F1FF5999F6FFFDFCFAFFFFFEFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFFFFFFEFF92BCFCFF1571FAFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178 + FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF2178FBFF2179 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FAFF1973FAFF3182FAFFE6F0 + FEFFFFFFFEFFFEFEFEFFFEFEFEFFFCFCFCFFFEFEFBFFE1EAF9FF3782F3FF1C70 + EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF1E73 + F1FF2C7DF5FFDCE8FAFFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFF7FAFEFF5598FBFF1672FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2078FAFF3181FBFFB8D4FDFFFFFF + FEFFFFFFFEFFFEFEFEFFFEFEFEFFFDFDFCFFFFFFFBFF88B5F8FF116CF2FF2173 + EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF2175 + F1FF156FF4FF80B0F9FFFFFFFCFFFEFEFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFDEEBFEFF3987FBFF1773FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF1974FAFF5E9EFBFFE7F0FEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFFFEFBFFCCDEF9FF2577F3FF1E72 + EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF2175 + F1FF1971F4FF468DF7FFF8FAFCFFFFFFFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFFFFFEFFC0D9FEFF2E81FCFF1672FBFF2178FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF1C76FAFF3D8AFBFFE3EDFEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFEFCFBFFFBFBF9FF5091F4FF186F + EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CCFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF2175 + F1FF156FF4FF6EA6F8FFFCFDFCFFFEFEFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFD2E4FEFF5296FCFF1772FBFF1772 + FBFF1E77FBFF2178FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FAFF1571FAFF619FFBFFFFFFFEFFFFFF + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFBFBFBFFFFFFF9FF84B1F4FF146C + EFFF2071EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2071E9FF2073EEFF1D72 + F1FF3080F5FFDEE9FAFFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFFFFFFFFFFFFB0D0FEFF65A2 + FCFF3081FCFF1A74FBFF1370FBFF1571FBFF1571FBFF1571FBFF1571FBFF1571 + FBFF1571FBFF1571FBFF1571FBFF1571FBFF1672FBFF1B75FBFF2179FBFF2279 + FBFF2279FBFF2279FBFF2279FAFF2279FAFF2078FAFF1D76FAFFBAD5FDFFFFFF + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFFFFEF9FFCADBF5FF2375 + EFFF1E70EBFF1F6FE4FF1E6BDEFF1D67D6FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE3FF2070E9FF2073EEFF146D + F1FF6AA2F6FFFFFFFBFFFEFDFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFCFDFEFFCBDFFDFFB4D2FDFFB4D2FDFFB7D4FEFFBDD7FEFFC8DE + FEFFE7F0FFFFABCCFEFF87B6FDFF78ADFDFF78ADFDFF78ADFDFF78ADFDFF78AD + FDFF78ADFDFF78ADFDFF78ADFDFF79AEFDFF71A9FDFF4991FCFF1B75FBFF2078 + FBFF2279FBFF2279FBFF2279FAFF2279FAFF2279FAFF1672FAFF63A0FBFFFFFF + FEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFFCFAF9FFF7F6F6FF5191 + F0FF176DEAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CBFF175BC0FF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADCFF1F6EE2FF2070E9FF1F72EEFF186F + F1FFA5C6F6FFFFFFFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFEBF3FEFF619EFCFF1E77FAFF1D76FAFF1D76FAFF1D76FAFF1571FBFF5297 + FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FBFFFF85B4FDFF1B75 + FBFF2178FBFF2279FAFF2279FAFF2279FAFF2279FAFF1D76FAFF3887FBFFF2F6 + FEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFEF6FF84B0 + F1FF1369EAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF175BC0FF3168B9F04071 + B9F01458BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1C70EEFF2C7B + F1FFE1EAF8FFFFFEFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FEFF7BAFFCFF0F6EFAFF2078FAFF2178FAFF2178FAFF2178FAFF1873FAFF5397 + FCFFFCFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71A9 + FCFF1873FAFF2279FAFF2279FAFF2279FAFF2279FAFF2178FAFF2078FAFFC1D9 + FDFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFEF6FF9BBE + F1FF166BEAFF1F6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF3168B9F04071 + B9F01458BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1B6FEEFF3A83 + F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFAFB + FEFF4991FBFF1A74FAFF2279FAFF2279FAFF2279FAFF2279FAFF1A74FAFF5497 + FBFFFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4D1 + FDFF1C75FAFF2178FAFF2279FAFF2279FAFF2279FAFF2279FAFF1471FAFF95BF + FCFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FFFFFCF6FFA8C5 + F1FF1A6DEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF3168B9F04171 + B9F01457BCFF1B61C9FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1B6FEEFF4288 + F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFF2F7 + FEFF3383FBFF1E77FAFF2279FAFF2279FAFF2279FAFF2279FAFF1A74FAFF5397 + FBFFFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFBFD9 + FDFF2178FAFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF126FFAFF8CBA + FCFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FFFFFCF6FFAEC9 + F1FF1B6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF3168B9F04171 + B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1B6FEDFF3C84 + F2FFF1F3F8FFFEFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFF6F9 + FEFF3D8AFBFF1B75FAFF2279FAFF2279FAFF2279FAFF2279FAFF1571FAFF63A0 + FBFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFEFFC7DD + FDFF1F77FAFF2078FAFF2279FAFF2279FAFF2279FAFF2279FAFF1672FAFFA1C6 + FCFFFFFFFEFFFEFEFEFFFDFDFDFFFDFDFDFFFBFBFBFFF9F9F9FFFFFCF6FFADC8 + F1FF1B6EEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF175ABFFF3268B9F04171 + B9F01457BBFF1B60C8FF1D65D3FF1E6ADBFF1F6DE2FF2070E9FF1C70EDFF307E + F1FFEAF0F8FFFFFDFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FEFF81B2FCFF116FFAFF2078FAFF2179FAFF2179FAFF1C75FAFF1D76FAFFB5D2 + FDFFFFFFFEFFFFFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2E4 + FFFFF1F7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFEFFF0F6 + FEFF428DFBFF1873FAFF2279FAFF2279FAFF2279FAFF1974FAFF3485FAFFEAF2 + FEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFEF6FF9FC0 + F1FF166BEAFF1E6EE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3268B9F04171 + B9F01457BBFF1B60C8FF1C65D2FF1E6ADBFF1F6DE2FF2070E9FF1F72EDFF1F73 + F1FFBAD3F7FFFFFFFBFFFCFCFCFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFFFF + FEFFF4F8FEFF6CA5FCFF1A74FAFF1672FAFF1873FAFF277CFAFFADCCFCFFFFFF + FEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF84B5 + FDFFD4E5FEFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFC8DEFDFF2A7EFAFF1371FAFF1772FAFF1370FAFF2178FAFFAFCEFDFFFFFF + FEFFFFFFFEFFFEFEFEFFFDFDFDFFFCFCFCFFFBFBFBFFF9F9F9FFFFFEF6FF81AE + F1FF136AEAFF1F6FE5FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3268B9F04171 + B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF2073EDFF146C + F1FF7BACF6FFFFFFFBFFFCFCFCFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFCFDFEFFC0D8FDFF9DC3FCFFA5C8FCFFDBE9FDFFFFFFFEFFFFFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFEDF4FEFF3483 + FAFFB6D3FDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFD8E7FDFF8DBAFCFF619FFBFF7FB2FCFFC6DCFDFFFFFFFEFFFFFF + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFCFCFCFFFAFAFBFFFDFCF9FFEAEFF6FF3C84 + EFFF1B6EEAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3267B9F04171 + B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF2073EDFF1A71 + F1FF438AF5FFF8F9FBFFFFFEFCFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFB7D3FDFF0F6E + FAFF73AAFCFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFDFDFDFFFDFDFDFFFCFCFCFFFAFAFAFFFFFFF9FF99BDF5FF156C + EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CCFF165ABFFF3267B9F04171 + B9F01457BBFF1B60C8FF1C65D2FF1E69DAFF1F6EE2FF2070E9FF2073EDFF2074 + F1FF1B72F4FFAACAF9FFFFFFFCFFFEFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFBFCFEFF609EFBFF1471 + FAFF297EFAFFE2EDFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFDFDFDFFFDFDFDFFFCFCFCFFFDFCFBFFFAFAF9FF5292F3FF176E + EFFF2071EAFF1F6FE4FF1E6BDDFF1D67D5FF1C62CBFF165ABFFF3267B9F04171 + B9F01457BBFF1B60C8FF1C65D2FF1E69DBFF1F6EE2FF2070E9FF2073EDFF2174 + F1FF1A71F3FF3B86F6FFE6EEFBFFFFFFFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFA7C9FCFF1A74FAFF2178 + FAFF1873FAFF77ADFBFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFCFCFFFFFFFBFF9ABFF7FF1A70F2FF2073 + EFFF2071EAFF1F6EE3FF1E6BDDFF1D67D5FF1C62CBFF165ABFFF3368B9F04272 + B9F01456BBFF1B60C7FF1C65D2FF1E69DBFF1F6DE1FF2070E8FF2072EDFF2174 + F0FF2175F4FF1670F5FF5D9BF8FFF6F9FDFFFFFFFDFFFDFDFDFFFDFDFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFFFEFEFFFFFFFEFFC8DDFDFF287CFAFF1E77FAFF2279 + FAFF2178FAFF1B75FAFFA8CAFCFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFDFDFDFFFDFDFDFFFDFDFDFFFFFFFCFFC9DCFAFF2A7BF4FF1C71F1FF2073 + EEFF2071EAFF1F6EE3FF1E6BDDFF1D66D5FF1C61CAFF165ABFFF3368B8F04272 + B9F01456BAFF1B60C7FF1C65D1FF1E69DAFF1F6DE1FF1F70E7FF2072ECFF2174 + F0FF2175F3FF2176F5FF1570F7FF66A1F9FFEFF5FCFFFFFFFDFFFEFDFDFFFDFD + FDFFFDFDFDFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFC3DAFDFF3081FAFF1B75FAFF2279FAFF2279 + FAFF2279FAFF1E77FAFF2279FAFFB2D0FDFFFFFFFEFFFFFFFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFD + FDFFFDFDFDFFFFFEFDFFFFFFFCFFCBDEFAFF3382F6FF1A71F3FF2174F1FF2073 + EDFF2071E9FF1F6EE3FF1E6ADCFF1D66D4FF1C61CAFF165ABEFF3368B8F04272 + B8F01456B9FF1B5FC6FF1C65D1FF1E69DAFF1F6DE0FF1F70E7FF2072ECFF2173 + EFFF2175F2FF2176F5FF2177F6FF1670F7FF4E92F9FFCEE0FBFFFFFFFDFFFFFF + FDFFFFFFFDFFFEFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFFFEFDFFFFFF + FDFFFFFFFDFFF7FAFDFF9BC1FBFF2379F9FF1C74F9FF2278F9FF2278F9FF2278 + F9FF2278F9FF2278F9FF1D75F9FF2178F9FF96BFFBFFFAFCFDFFFFFFFDFFFFFF + FDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFFFE + FDFFFFFFFDFFFFFFFCFFB2CFFAFF2E7FF7FF1972F5FF2175F3FF2174F0FF2072 + EDFF2070E8FF1F6EE2FF1E6ADCFF1D66D3FF1B61C9FF1659BDFF3367B7F04372 + B8F01355B8FF1B5FC5FF1C64CFFF1D68D8FF1E6CDFFF1F6FE6FF2071EBFF2173 + EEFF2175F1FF2176F4FF2177F5FF2177F6FF1972F7FF267AF7FF83B2FAFFC8DD + FBFFF0F4FCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFEFCFFE7F0 + FCFFB3D0FBFF5697FAFF1872F8FF1E76F8FF2278F8FF2278F8FF2278F8FF2278 + F8FF2278F8FF2278F8FF2278F8FF1F76F8FF1872F8FF5D9CFAFFBED7FBFFEFF4 + FCFFFFFFFCFFFFFFFCFFFFFEFCFFFFFEFCFFFFFEFCFFFFFFFCFFFFFFFCFFFBFB + FCFFCBDFFAFF6DA5F8FF1A73F6FF1B73F5FF2176F4FF2175F2FF2173EFFF2072 + ECFF1F70E7FF1F6DE1FF1E69DBFF1C65D2FF1B61C8FF1658BCFF3267B7F04472 + B7F01355B7FF1B5EC4FF1C63CEFF1D68D7FF1E6BDEFF1F6FE4FF2070E9FF2072 + EDFF2174F0FF2175F2FF2176F4FF2176F5FF2177F6FF1E75F6FF1771F6FF2076 + F7FF3C87F7FF7AADF8FFA1C5F9FFB9D3FAFFB0CEFAFF96BEF9FF5F9DF8FF2D7F + F7FF1B73F7FF1872F7FF2077F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177 + F7FF2177F7FF2177F7FF2177F7FF2177F7FF2077F7FF1771F7FF1D74F7FF3A87 + F7FF8DB8F9FFCBDEFAFFEEF3FAFFF1F4FAFFEFF3FAFFD5E4FAFF9BC1F9FF5193 + F7FF2177F6FF1771F6FF1F76F5FF2176F4FF2175F3FF2174F1FF2073EEFF2071 + EAFF1F6FE6FF1F6CE0FF1E69D9FF1C65D1FF1B5FC7FF1658BBFF3267B6F04472 + B7F01354B5FF1A5EC2FF1C62CCFF1D67D5FF1E6ADCFF1F6EE2FF2070E7FF2071 + EBFF2173EEFF2174F1FF2175F2FF2175F3FF2176F4FF2176F5FF2176F5FF1F75 + F5FF1C73F5FF146FF5FF1770F5FF1E75F5FF1C73F5FF146FF5FF1770F5FF1D74 + F5FF2076F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177 + F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF2177F5FF1F76F5FF1B73 + F5FF1570F5FF2378F5FF2F7EF5FF3683F5FF307FF5FF267AF5FF1770F5FF1871 + F4FF1F75F5FF2176F4FF2176F4FF2175F3FF2174F1FF2173EFFF2072ECFF2070 + E8FF1F6EE4FF1E6BDEFF1D68D7FF1C64CFFF1B5FC5FF1658B9FF3367B5F04774 + B6F01253B2FF1A5CC0FF1B61C9FF1D65D2FF1E69DAFF1F6CDFFF1F6EE4FF2070 + E8FF2071EBFF2073EDFF2173EFFF2174F0FF2174F1FF2175F2FF2175F2FF2175 + F2FF2175F2FF2175F2FF2074F2FF1F73F2FF1F74F2FF2174F2FF2175F2FF2175 + F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175 + F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175F2FF2175 + F2FF2074F2FF1E73F2FF1D72F2FF1C72F2FF1D72F2FF1E73F2FF2074F2FF2175 + F2FF2175F2FF2174F1FF2175F1FF2174EFFF2173EEFF2072ECFF2070E9FF1F6F + E6FF1F6DE1FF1E6ADBFF1D67D5FF1C62CCFF1B5EC2FF1556B6FF3567B4F04D77 + B6F01150AFFF1A5BBCFF1B5FC6FF1C64CFFF1D68D6FF1E6BDCFF1F6DE0FF1F6E + E4FF2070E8FF2071EAFF2071EBFF1F72ECFF1E71ECFF1D71EEFF1D71EDFF1D70 + EDFF1D70EDFF1D70EDFF1D70EDFF1D70EEFF1D70EEFF1D70EEFF1D70EEFF1D71 + EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71 + EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D71EEFF1D70 + EEFF1D70EEFF1D70EEFF1D70EEFF1D70EEFF1D70EDFF1D70EDFF1D70EDFF1D70 + EDFF1D71EDFF1E71EDFF1F71ECFF2072ECFF2071EBFF2071E8FF1F6FE5FF1F6D + E1FF1E6BDDFF1D68D8FF1D65D1FF1B61C8FF1A5CBFFF1454B3FF3869B2F1597E + B7F80F4DABFF1959B8FF1A5EC2FF1C62CAFF1D66D2FF1E68D8FF1E6ADCFF1F6C + DFFF1F6DE2FF1D6EE4FF1C6DE5FF1F70E7FF2976EAFF347EECFF3C83EEFF4287 + EEFF468AEFFF498CF0FF4A8DF0FF4C8DF1FF4C8EF1FF4D8EF1FF4D8EF1FF4D8F + F1FF4D8FF1FF4D8FF1FF4D8FF1FF4D8EF1FF4D8EF1FF4D8EF1FF4D8EF1FF4D8E + F1FF4D8EF1FF4D8EF1FF4D8EF1FF4D8FF1FF4D8FF1FF4D8FF1FF4D8FF1FF4D8E + F1FF4D8EF1FF4C8EF1FF4C8EF1FF4B8DF0FF498CF0FF478AEFFF4388EFFF3E84 + EEFF377FEDFF2C79EBFF2271E8FF1C6DE6FF1C6DE5FF1E6EE2FF1F6DE0FF1E6B + DDFF1E69D9FF1D67D4FF1C63CCFF1B5FC4FF1A5BBBFF1351AFFF3D6CB0ED5E82 + B6B80D4AA6FF1957B3FF1A5BBCFF1A5FC5FF1B63CBFF1D65D1FF1D67D7FF1D68 + D9FF1A68DCFF2672E0FF4889EAFF659DF0FF75A8F4FF7EAEF5FF82B1F7FF84B2 + F7FF86B3F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4 + F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4 + F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4 + F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF87B4F7FF86B3F7FF85B2F7FF83B1 + F7FF7FAEF6FF78A9F5FF6BA1F1FF5290ECFF3078E3FF1B68DCFF1C68DAFF1E68 + D8FF1D66D3FF1C63CEFF1B60C7FF1A5CBFFF1858B6FF1652ABFF2258A7BB255A + A978164FA4FF1754ADFF1858B6FF1A5CBEFF1B5FC5FF1C62CBFF1A62CEFF1C65 + D2FF4281E0FF72A3EFFF86B2F4FF89B4F5FF87B3F5FF86B2F5FF86B2F5FF85B2 + F5FF85B2F5FF85B2F5FF85B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1 + F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1 + F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1F5FF84B1 + F5FF84B1F5FF84B1F5FF84B1F5FF85B1F5FF85B2F5FF85B2F5FF85B2F5FF86B2 + F5FF86B2F5FF86B3F5FF88B4F5FF87B3F5FF7AAAF1FF528DE5FF236AD5FF1862 + CFFF1B62CCFF1B60C7FF1A5DC0FF1959B8FF1855B0FF154FA5FF2056A6B7527C + B9881950A1FF1650A8FF1855B0FF1958B7FF1A5CBEFF195DC3FF2367CBFF4C86 + DDFF7FACF0FF89B3F3FF85B0F3FF85B1F3FF85B1F3FF85B1F3FF85B1F4FF85B1 + F4FF85B1F4FF85B2F4FF85B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2 + F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2 + F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2F4FF86B2 + F4FF86B2F4FF86B2F4FF86B2F4FF85B2F4FF85B2F4FF85B1F4FF85B1F4FF85B1 + F4FF85B1F3FF85B1F3FF85B1F3FF85B1F3FF87B2F3FF86B1F2FF5E94E3FF2D6F + D0FF1A5EC4FF1A5CBFFF1A5AB9FF1956B2FF1752AAFF0F499EFF416DAEBC7796 + C5481B509EFA144CA0FF1752A9FF1855AFFF1757B5FF1F5EBDFF5085D7FF7DAA + ECFF88B3F1FF86B2F1FF86B2F2FF86B1F2FF85B0F1FF83AFF1FF82AEF1FF81AE + F0FF81ADF0FF80ACF0FF7FACF0FF7FACF0FF7FABEFFF7EABEFFF7EABEFFF7EAB + EFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7FACEFFF7FACEFFF7FACEFFF7FAC + EFFF7FACEFFF7FACEFFF7FACEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EAB + EFFF7EABEFFF7FABEFFF7FABEFFF7FACF0FF80ACF0FF80ADF0FF81AEF0FF82AE + F0FF83AFF1FF84B0F1FF85B1F2FF86B1F2FF86B1F1FF87B2F1FF84B0EFFF6294 + DFFF2866C2FF1757B6FF1855B1FF1853AAFF174FA3FF0C4599FF5F83B9A196AE + D2033A66A8E80C4496FF164EA1FF1751A7FF1753ACFF1B58B2FF2B66BEFF3871 + C6FF3F77CCFF417ACEFF4079CEFF3D77CEFF3974CDFF3571CBFF326FCAFF2F6D + C9FF2E6CC9FF2C6BC8FF2B6AC8FF2A69C8FF2A69C7FF2968C7FF2968C7FF2968 + C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968 + C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968C7FF2968 + C7FF2969C7FF2969C7FF2A69C7FF2A6AC8FF2B6AC8FF2D6CC9FF2F6DC9FF316E + CAFF3471CBFF3873CCFF3C77CDFF3F78CEFF4179CEFF4078CCFF3A73C8FF2F69 + C1FF1E5BB4FF1753ADFF1751A8FF174FA2FF114799FF1F519CFCB4C2D95CD0D9 + E500839FC97B0E4494FF0D4394FF134A9DFF164EA2FF1751A6FF1450A8FF1350 + AAFF1350ADFF1251AEFF1352B0FF1352B1FF1453B2FF1453B2FF1554B3FF1555 + B4FF1555B4FF1555B4FF1555B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655 + B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1656 + B4FF1656B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655B4FF1655 + B4FF1655B4FF1655B4FF1655B4FF1555B4FF1555B4FF1555B4FF1555B4FF1555 + B3FF1454B3FF1453B2FF1353B1FF1352B0FF1251AFFF1351ADFF1350ABFF1450 + A9FF1650A7FF164FA3FF154C9EFF114797FF0F4493FF5B7FB8A6CED7E400C5D1 + E2009EB5D8006589C17C3562A5E01C4F9BFF184C9BFF154B9DFF0A449AFF0C46 + 9DFF0F49A0FF104BA2FF114CA3FF124DA4FF124DA5FF124DA6FF124EA6FF124E + A6FF124EA6FF124EA6FF124EA6FF124EA6FF134EA7FF134EA7FF134EA7FF134F + A7FF134FA7FF134FA7FF134FA7FF134FA7FF134FA7FF134FA8FF134FA8FF134F + A7FF134FA7FF134EA7FF134EA7FF134EA7FF134EA7FF134EA7FF134EA7FF134E + A7FF134EA7FF134EA7FF124EA6FF124EA6FF124EA6FF124EA6FF124EA6FF124E + A6FF124DA6FF124DA5FF124DA4FF114CA4FF104BA2FF0F4AA0FF0E479DFF0A44 + 9BFF0E469BFF184E9DFF184C99FF22539CEE4A74B4A59AB2D70DA4BAD900C5D1 + E20094AED40097B0D60083A2CF0B86A2CE604775B9801C51A0866487BAE9597F + B9F04973B4F0406EB2F03C6BB1F03A69B1F03968B0F03767B0F03767B0F03667 + B0F03666B0F03666B0F03666B0F03666B0F03566B0F03466B0F03466B0F03466 + B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466 + B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466B0F03466 + B0F03466B0F03466AFF03666B0F03666B0F03666B0F03666B0F03666B0F03767 + B0F03767B0F03868B1F03A69B0F03C6BB1F03F6EB2F04773B4F0537BB7F06D8D + BEF04570AFB82F62AF805E86BF606D90C60F6D90C80094ADD300A8BCDA00F000 + 000000000007C000000000000003800000000000000180000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000080000000000000008000 + 0000000000008000000000000001C000000000000003F00000000000000F2800 + 0000300000006000000001002000000000008025000000000000000000000000 + 00000000000097A7BD0091A2BC006B89B4544671B38F3D6EB7B82763BCB82965 + BED43A6FC0F0376FC3F0366FC4F03770C5F0376FC5F0376FC6F03770C6F03770 + C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770 + C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770C6F03770 + C6F03770C6F03770C6F03770C6F03770C6F03770C6F0376FC5F03770C5F0376F + C4F0376FC3F0396EC0F03069BEE02864BDB83D6EB8B8416FB39C6C89B36D9CA8 + BC07B4B8BF008EA1BE006486B58A1C57AEFF1657B7FF155AC0FF1A5FC7FF1A61 + CCFF1862D0FF1863D3FF1964D4FF1964D5FF1964D6FF1964D6FF1965D6FF1965 + D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965 + D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965 + D6FF1965D6FF1965D6FF1965D6FF1965D6FF1965D6FF1964D6FF1964D5FF1964 + D5FF1863D3FF1862D1FF1962CDFF195FC9FF155AC2FF1557BAFF1755B0FF5B7F + B4BFA9B2C007718FBB2A2059AEFC1455B6FF1A5EC2FF1C62CBFF1D66D3FF1D68 + D8FF1E6ADBFF1F6BDDFF1E6CDFFF1E6CDFFF1E6DE0FF1E6DE0FF1E6DE0FF1E6D + E1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6D + E1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6D + E1FF1E6DE1FF1E6DE1FF1E6DE1FF1E6DE0FF1E6DE0FF1E6DE0FF1E6CDFFF1E6C + DFFF1F6BDEFF1E6ADBFF1E69D8FF1D66D4FF1C63CCFF1B5FC4FF1658B9FF1753 + ADFF6F8DB95E6889BA741755B1FF195BBEFF1C62CAFF1D66D3FF1E6ADBFF1F6D + DFFF1F6EE3FF1F6FE5FF2070E7FF2070E7FF2071E8FF2071E8FF2071E9FF2071 + E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071 + E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071 + E9FF2071E9FF2071E9FF2071E9FF2071E9FF2071E8FF2071E8FF2071E8FF2070 + E7FF1F70E6FF1F6EE4FF1F6DE0FF1E6ADCFF1D67D5FF1C63CCFF1B5DC1FF1152 + B2FF4B76B5A13869B3821857B6FF1B5FC4FF1C64D0FF1E69DAFF1F6DE1FF2070 + E6FF2071EAFF2072EDFF2173EEFF2173EEFF2173EFFF2173F0FF2173F0FF2173 + F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173 + F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173 + F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173F0FF2173EFFF2173EFFF2173 + EEFF2072EDFF2072EBFF2070E7FF1F6EE2FF1E6ADCFF1D65D3FF1B61C7FF1557 + B8FF3165B3B95D82B9D11053B8FF1C61C9FF1D67D5FF1E6CDEFF1F70E5FF2072 + EBFF2173EEFF2175F1FF2175F2FF2176F3FF2176F3FF2176F4FF2176F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176 + F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F4FF2176F3FF2176F3FF2175 + F3FF2175F2FF2173EFFF2072ECFF2070E6FF1F6DE0FF1D68D7FF1C62CBFF1759 + BCFF3065B3C8557EBAF51155BCFF1C63CCFF1D68D8FF1F6DE1FF2071E8FF2173 + EEFF2174F2FF2176F4FF2176F6FF2177F6FF2177F6FF2177F6FF2177F6FF2177 + F6FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177 + F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177F7FF2177 + F7FF2177F7FF2177F7FF2177F7FF2177F6FF2177F6FF2177F6FF2177F6FF2176 + F6FF2176F4FF2175F3FF2173EFFF2071EAFF1F6EE2FF1E6ADAFF1C64CFFF1559 + BFFF3E6FB7F04B77BAF01457BEFF1C63CEFF1E69DAFF1F6EE2FF2072EBFF2174 + EFFF2176F3FF2177F6FF2177F7FF2278F8FF2177F8FF1E76F8FF1872F8FF1470 + F8FF126FF8FF136FF8FF1771F8FF1D75F8FF2177F9FF2278F9FF2278F9FF2278 + F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2278F9FF2178F8FF1E76 + F8FF1973F8FF1671F8FF1570F8FF1671F8FF1A73F8FF1E75F8FF2177F8FF2177 + F7FF2177F6FF2176F5FF2175F1FF2072ECFF1F6FE4FF1E6ADCFF1C65D1FF165A + C2FF376AB8F04674BAF01458BEFF1C63CFFF1E6ADBFF1F6FE4FF2072EBFF2175 + F1FF2176F5FF2177F7FF2278F8FF1E76F9FF1B74F9FF3080FAFF5E9CFBFF80B1 + FBFF90BBFBFF89B7FCFF6DA6FBFF3B88FAFF1B75FAFF1D76F9FF2279FAFF2279 + FAFF2279FAFF2279FAFF2279FAFF2279FAFF2279FAFF2077FAFF1973F9FF2E7F + FAFF5799FBFF73A9FBFF7AADFBFF71A8FBFF4F94FAFF2C7EF9FF1A73F9FF1F76 + F8FF2177F7FF2177F5FF2175F2FF2073EDFF1F6FE6FF1E6BDDFF1D65D2FF175C + C3FF3469B9F04473BAF01458BFFF1C64D0FF1E6ADBFF1F6FE4FF2072ECFF2175 + F1FF2177F5FF2177F7FF1973F8FF287CFAFFA1C5FCFFE8F1FDFFFFFFFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFFFFFEFFF0F6FEFFA9CBFDFF2B7FFBFF1973FAFF2279 + FAFF2279FAFF2279FAFF2279FAFF2279FAFF1C75FAFF1E76FAFF88B7FCFFE2ED + FEFFFDFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFBFCFEFFE5EFFDFF90BBFBFF2077 + F8FF1A73F7FF2177F6FF2175F2FF2073EEFF2070E6FF1E6BDEFF1D65D2FF175C + C3FF3369B9F04473BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF2072EDFF2175 + F2FF2177F5FF1872F7FF3D89F9FFC6DCFDFFFFFFFEFFFFFFFEFFFFFFFEFFFFFE + FEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFCDE0FDFF438DFBFF1873 + FAFF2279FAFF2279FAFF2279FAFF1A75FAFF3082FBFFB2D1FDFFFFFFFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFBBD5 + FCFF3684F8FF1871F6FF2176F3FF2172EEFF2070E6FF1E6BDEFF1D66D3FF175C + C4FF3269BAF04373BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF2072EDFF2175 + F2FF1972F5FF3F89F9FFDFEBFDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFE6F0FEFF488F + FBFF1974FAFF2379FAFF1C76FAFF3383FBFFD0E2FDFFFFFFFEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FDFFD7E6FCFF3985F7FF1A72F3FF2173EEFF2070E7FF1E6CDEFF1D66D3FF175C + C4FF3269BAF04273BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF2072EDFF1E73 + F2FF2579F6FFCCDFFBFFFFFFFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFD8E7 + FEFF2E80FBFF1B75FAFF2078FAFFBED7FDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFD + FDFFFFFFFCFFC6DBFAFF2478F3FF1E72EEFF2070E7FF1E6CDEFF1D66D3FF175C + C4FF3268BAF04273BAF01559C1FF1C65D0FF1E6ADCFF1F6FE5FF2072EDFF166E + F2FF80B0F7FFFFFFFCFFFEFEFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFEFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FFFF93BDFEFF0B6BFAFF74ABFCFFFFFFFFFFFFFFFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFD + FDFFFDFDFCFFFFFFFBFF7EAEF5FF166DEEFF2070E7FF1E6CDEFF1D66D3FF175D + C4FF3268BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF1E71EDFF2577 + F2FFD4E4F9FFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFD1E3 + FDFF9FC4FCFFBBD5FDFFFDFDFEFFFFFFFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFF + FFFFE7F0FFFF3686FCFFCDE1FFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFF + FEFFFFFFFEFFD9E8FDFFBED8FDFFE6EFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFDFD + FDFFFCFCFDFFFFFFFBFFD6E3F7FF2677EEFF1D6FE7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF196EEDFF498D + F3FFF7F8FAFFFEFEFCFFFDFDFDFFFEFEFEFFFFFFFEFFFFFFFEFFA7CAFDFF2379 + FBFF1572FAFF1974FAFF6EA7FCFFF7FAFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFAECEFEFFEFF5FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFEFF + FEFF91BCFCFF257BFBFF1D76FAFF3081FBFFB5D2FDFFFFFFFEFFFFFFFEFFFDFD + FDFFFDFDFDFFFEFCFBFFF7F8F7FF4F8FEFFF186CE7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF156DECFF679F + F4FFFEFCFAFFFDFCFCFFFDFDFDFFFEFEFEFFFFFFFEFFE9F1FEFF3082FAFF1974 + FAFF2279FAFF2077FAFF1471FAFFAECEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFF8FBFFFFFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2DA + FEFF1C75FAFF1C76FAFF2078FAFF1973FAFF2E80FAFFD9E7FDFFFFFFFEFFFEFE + FEFFFDFDFDFFFBFBFBFFFEFCF7FF72A4EFFF1469E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D1FF1E6BDCFF1F6FE5FF146BECFF73A7 + F4FFFFFEFAFFFDFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFC3DAFDFF2078FAFF2078 + FAFF2279FAFF2279FAFF126FFAFF87B6FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91BC + FDFF1270FAFF2279FAFF2279FAFF2279FAFF116FFAFF90BCFCFFFFFFFEFFFEFE + FEFFFCFCFCFFFBFBFBFFFFFEF7FF7FADF0FF1268E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE6FF156CEDFF6DA3 + F4FFFFFEFAFFFEFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFD1E2FDFF257BFAFF2078 + FAFF2279FAFF2279FBFF1773FBFF6BA6FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90BC + FDFF1270FBFF2279FAFF2279FAFF2279FAFF1370FAFF8FBBFCFFFFFFFEFFFEFE + FEFFFCFCFCFFFBFBFBFFFFFDF7FF78A8F0FF1369E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE6FF166CEDFF639D + F4FFFDFCFAFFFEFDFCFFFEFEFEFFFEFEFEFFFFFFFEFFEBF3FEFF2E80FAFF1F77 + FAFF2279FBFF2279FBFF1E77FBFF3483FCFFEBF3FFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95BE + FDFF1170FBFF2279FBFF2279FAFF2279FAFF1370FAFF90BCFCFFFFFFFEFFFEFE + FEFFFCFCFCFFFDFBFBFFFBFAF7FF619AF0FF166AE7FF1E6CDFFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF196EEDFF488C + F3FFF6F8FAFFFFFEFCFFFEFEFEFFFEFEFEFFFFFFFEFFFCFDFEFF5497FBFF1873 + FAFF2279FBFF2279FBFF2279FBFF1B75FBFF5598FCFFB7D3FEFFCDE1FEFFCEE1 + FEFFCDE1FEFFCDE1FEFFCDE1FEFFCDE1FEFFCDE1FEFFCDE1FEFFCEE1FEFF78AD + FDFF1672FBFF2279FBFF2279FAFF2279FAFF126FFAFF8DBAFCFFFFFFFEFFFEFE + FEFFFCFCFCFFFFFDFBFFEBF1F7FF3881EFFF1B6DE7FF1E6CDFFF1D66D4FF175D + C4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF1D71EDFF2A7A + F3FFDFEAFAFFFFFFFCFFFEFEFEFFFEFEFEFFFFFEFEFFFFFFFEFFA5C8FDFF1672 + FAFF2279FBFF2279FBFF2279FBFF2279FBFF1A74FBFF1E77FBFF257BFBFF257B + FBFF257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF257BFBFF237A + FBFF2279FBFF2279FBFF2279FBFF2178FAFF1471FAFFA7CAFDFFFFFFFEFFFEFE + FEFFFDFCFCFFFFFFFBFFAAC8F7FF196EEEFF1F70E7FF1E6CDFFF1C66D4FF175D + C5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF2072EDFF1970 + F2FFA8C9F9FFFFFFFCFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFF6FAFEFF4F94 + FBFF1772FBFF2279FBFF2279FBFF2279FBFF2279FBFF2078FBFF2078FBFF2078 + FBFF2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF2078FBFF2178 + FBFF2279FBFF2279FBFF2179FBFF1471FAFF5E9EFCFFF6FAFEFFFFFFFEFFFEFE + FEFFFFFFFDFFF0F4FAFF458BF4FF196FEFFF2070E7FF1E6CDFFF1C66D4FF175D + C5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF2072EDFF176F + F2FF5A99F7FFF9FBFCFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFD4E5 + FEFF3081FCFF1874FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2178FBFF2179FBFF8DBAFCFFF2F7FEFFFFFFFEFFFEFEFEFFFEFE + FEFFFFFFFDFFEFF4FAFF3F87F4FF1A6FEFFF2070E7FF1E6CDFFF1C66D4FF175D + C5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF2072EDFF1E73 + F3FF2679F6FFDFEAFCFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFCADFFFFF3A88FCFF1370FBFF1E77FBFF2178FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279FBFF2279 + FBFF2279FBFF2279FBFF1B75FAFF87B7FDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFDFCFCFFFFFFFBFF8DB7F6FF156DEEFF2070E7FF1E6CDFFF1D66D4FF175D + C5FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE5FF2072EDFF176F + F2FF6BA4F7FFFAFCFCFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FEFFFFFFFFFFEFF5FFFF83B4FDFF3383FCFF1974FBFF1370FBFF1571FBFF1571 + FBFF1571FBFF1571FBFF1571FBFF1571FBFF1571FBFF1873FBFF1F77FBFF2279 + FBFF2279FBFF2279FBFF2078FAFF1E77FAFFC4DBFEFFFFFFFEFFFEFEFEFFFEFE + FEFFFCFCFCFFFFFFFBFFCDDDF7FF2375EFFF1E6FE7FF1E6CDFFF1D66D4FF175D + C4FF3168BAF04172BAF0155AC1FF1D64D1FF1E6BDCFF1F6FE6FF1E72EDFF2175 + F2FFC9DCF9FFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFD8E7 + FDFFB4D2FDFFBAD5FDFFB7D3FEFFB9D4FEFFAACCFEFF89B7FDFF78ADFDFF78AD + FDFF78ADFDFF78ADFDFF78ADFDFF79ADFDFF79AEFDFF63A1FDFF277CFCFF1E77 + FBFF2279FBFF2279FAFF2279FAFF1571FAFF6AA5FCFFFFFFFEFFFFFFFEFFFEFE + FEFFFCFCFCFFFEFDFBFFF0F3F7FF3C84EFFF1A6DE7FF1E6CDFFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D1FF1E6BDCFF1F6FE5FF1A6FEDFF4389 + F3FFF4F6FAFFFFFEFCFFFDFDFDFFFEFEFEFFFFFFFEFFFFFFFEFF98C0FDFF237A + FAFF1C76FAFF1D76FAFF106EFAFF88B7FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2DAFEFF2A7E + FBFF1F77FBFF2279FAFF2279FAFF1E77FAFF3182FAFFE7F0FEFFFFFFFEFFFEFE + FEFFFCFCFCFFFCFBFBFFFAF9F7FF5F99EFFF166AE7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04071BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF166CECFF649D + F4FFFDFCFAFFFDFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFEBF3FEFF2E80FAFF1A74 + FAFF2178FAFF2178FAFF126FFAFF84B4FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70A8 + FCFF1772FAFF2279FAFF2279FAFF2279FAFF1973FAFFA7CAFDFFFFFFFEFFFEFE + FEFFFDFDFDFFFBFBFBFFFFFCF7FF74A5EFFF1369E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF146BECFF70A4 + F4FFFFFDFAFFFDFCFCFFFDFDFDFFFEFEFEFFFFFFFEFFC9DEFDFF2279FAFF2078 + FAFF2279FAFF2279FAFF1370FAFF82B4FCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8DBA + FCFF136FFAFF2279FAFF2279FAFF2279FAFF116FFAFF8BB9FCFFFFFFFEFFFDFD + FDFFFDFDFDFFFBFBFBFFFFFEF7FF7EACF0FF1268E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF156BECFF6CA3 + F4FFFFFDFAFFFDFCFCFFFDFDFDFFFEFEFEFFFFFFFEFFD6E6FDFF2379FAFF1E77 + FAFF2279FAFF2279FAFF126FFAFF9BC3FDFFFFFFFEFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFF9AC2 + FCFF1270FAFF2279FAFF2279FAFF2178FAFF1672FAFFAACCFDFFFFFFFEFFFDFD + FDFFFDFDFDFFFBFBFBFFFFFEF7FF7DABF0FF1268E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1D65D0FF1E6ADCFF1F6FE5FF186DEDFF5494 + F3FFFAFAFAFFFEFDFCFFFDFDFDFFFEFEFEFFFFFFFEFFFEFEFEFF7AAEFCFF126F + FAFF1571FAFF116FFAFF468EFBFFE8F1FEFFFFFFFEFFFEFEFEFFFFFFFFFFFFFF + FFFFFFFFFFFFD6E6FFFFF5F9FFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFEFFDFEC + FDFF3585FBFF1370FAFF1B75FAFF0F6EFAFF5D9CFCFFF8FBFEFFFFFFFEFFFDFD + FDFFFCFCFCFFFCFBFAFFFDFBF7FF699FEFFF156AE7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF0155AC1FF1C65D0FF1E6ADCFF1F6FE5FF1C70EDFF2F7D + F2FFE5EDF9FFFFFFFCFFFDFDFDFFFEFEFEFFFEFEFEFFFFFFFEFFFAFCFEFFA2C6 + FDFF6BA6FCFF87B6FCFFE4EEFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFF3F7FEFF5E9DFBFFE8F1FEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFD0E3FDFF65A2FCFF468FFBFF77ACFCFFE8F1FEFFFFFFFEFFFEFEFEFFFDFD + FDFFFCFCFCFFFEFDFAFFF0F3F7FF3E85EEFF1A6DE7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04172BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF2072EDFF186F + F2FF9EC2F8FFFFFFFCFFFEFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFF + FEFFAECEFDFF116FFAFFB1CFFDFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFFFFFEFFF8FBFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFDFD + FDFFFCFCFCFFFFFFFAFFBAD2F6FF1C71EEFF1E70E7FF1E6CDEFF1D66D3FF175D + C4FF3168BAF04272BAF01559C0FF1C64D0FF1E6ADCFF1F6FE5FF2072EDFF1B71 + F2FF3C86F6FFEBF1FCFFFFFFFDFFFDFDFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFFFFFEFFFFFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFF3F8 + FEFF478FFBFF116FFAFF4E93FBFFF8FBFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFD + FDFFFFFEFCFFF8F9FAFF5494F4FF186DEEFF2070E7FF1E6BDEFF1D66D3FF175C + C4FF3268BAF04272BAF01559C0FF1C64D0FF1E6ADBFF1F6FE4FF2072ECFF2175 + F2FF1670F5FF67A1F9FFFBFCFDFFFFFFFDFFFDFDFEFFFEFEFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFEFFFFFFFEFF74AB + FCFF1773FAFF2279FAFF1772FAFF87B6FCFFFFFFFEFFFFFFFEFFFEFEFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFDFDFFFFFF + FDFFFFFFFCFF89B6F8FF176FF2FF2073EEFF2070E6FF1E6BDEFF1D66D3FF175C + C3FF3268BAF04272BAF01558BFFF1C63CFFF1E6ADBFF1F6FE4FF2072EBFF2175 + F0FF2076F5FF1771F6FF6EA6FAFFF0F6FDFFFFFFFDFFFFFFFDFFFDFDFDFFFDFD + FDFFFEFEFEFFFEFEFEFFFDFDFEFFFFFFFEFFFFFFFEFFF4F8FEFF77ACFCFF1973 + FAFF2179FAFF2279FAFF2078FAFF1B75FAFF92BDFCFFFFFFFEFFFFFFFEFFFEFE + FEFFFEFEFEFFFEFEFEFFFDFDFDFFFDFDFDFFFDFDFDFFFDFDFDFFFFFFFDFFFFFF + FDFF91BBF9FF1B73F5FF1F74F2FF2073EDFF1F6FE6FF1E6BDDFF1D65D2FF175C + C3FF3268B9F04272B9F01458BEFF1C63CEFF1E69DAFF1F6EE3FF2071EBFF2174 + F0FF2176F3FF2076F6FF1671F7FF498FF8FFB7D2FBFFF8FAFCFFFFFFFDFFFFFF + FDFFFFFFFDFFFFFFFDFFFFFFFDFFFFFFFDFFBCD6FCFF4E93FAFF1771F9FF2177 + F9FF2278F9FF2278F9FF2278F9FF1F77F9FF1A73F9FF6AA4FAFFDCE9FCFFFFFF + FDFFFFFFFDFFFFFFFCFFFFFEFDFFFFFFFDFFFFFFFDFFFFFFFDFFDCE9FBFF6BA4 + F9FF1972F6FF1E74F4FF2175F1FF2072ECFF1F6FE4FF1E6ADCFF1D64D1FF175B + C2FF3268B8F04372B8F01357BCFF1C63CCFF1D68D9FF1F6DE1FF2071E9FF2173 + EEFF2174F2FF2176F5FF2177F6FF1972F6FF1B73F6FF4C90F8FFA1C5FAFFC5DA + FAFFD4E3FAFFCBDEFAFFB0CEFAFF65A1F8FF1C74F7FF1872F7FF2178F8FF2278 + F8FF2278F8FF2278F8FF2278F8FF2278F8FF2077F7FF1570F7FF3181F8FF96BE + FAFFD3E3FAFFECF2FBFFF1F5FBFFEAF0FBFFC9DDFAFF96BEF9FF3281F7FF1570 + F5FF2075F4FF2175F3FF2174EFFF2071EAFF1F6EE3FF1E6ADBFF1C63CFFF175A + C0FF3268B7F04473B7F01356BAFF1C61CAFF1D67D6FF1E6CDFFF2070E6FF2072 + ECFF2174F0FF2175F2FF2176F3FF2176F4FF2075F5FF1972F5FF1770F5FF2277 + F5FF267AF5FF2478F5FF1B73F5FF1770F5FF1F76F5FF2177F5FF2176F5FF2176 + F5FF2176F5FF2176F5FF2176F5FF2176F5FF2177F5FF2177F5FF1C73F5FF1871 + F5FF2679F5FF2E7FF5FF3582F5FF2D7EF5FF2277F5FF1770F4FF1C73F4FF2176 + F4FF2175F2FF2174F0FF2072EDFF1F71E8FF1F6DE1FF1D68D8FF1C63CDFF165A + BEFF3368B6F04975B7F01354B6FF1B60C6FF1D65D2FF1E6ADBFF1F6EE2FF2071 + E8FF2072EBFF2072EEFF1F73EFFF1E72F0FF1E73F0FF1D72F0FF1C71F0FF1B70 + F0FF1A70F0FF1A70F0FF1B71F0FF1C72F0FF1D72F0FF1C72F0FF1C72F0FF1C72 + F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF1C72F0FF1D72F0FF1C71 + F0FF1A70F0FF196FF0FF186FF0FF1970F0FF1B71F0FF1D72F0FF1F73F0FF1F73 + EFFF2073EFFF2072ECFF2071E9FF1F6EE4FF1E6BDDFF1D66D4FF1C61C9FF1557 + BAFF3669B4F1587FB8F81050B0FF1B5EC1FF1C63CDFF1D67D6FF1E6BDDFF1F6E + E2FF1D6DE5FF1B6DE7FF2071E9FF2A78ECFF317CEEFF3680EEFF3982EEFF3B83 + EFFF3C84EFFF3C84EFFF3D84EFFF3D85EFFF3D85EFFF3D85EFFF3D84EFFF3D84 + EFFF3D84EFFF3D84EFFF3D84EFFF3D84EFFF3D85EFFF3D85EFFF3D85EFFF3D84 + EFFF3C84EFFF3C84EFFF3B83EFFF3982EEFF3680EEFF327DEEFF2C79ECFF2273 + EAFF1C6DE7FF1C6DE5FF1F6EE2FF1F6CDEFF1E68D7FF1C64CFFF1A5EC4FF1455 + B5FF3B6BB2ED4C75B2B8104EAAFF195ABAFF1B5FC5FF1C64CEFF1C67D5FF1966 + D9FF2871E0FF4D8CEBFF6AA0F2FF79ABF5FF80AEF6FF82B1F6FF84B2F6FF84B2 + F6FF84B2F6FF84B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2 + F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2F6FF85B2 + F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF83B1F6FF80AFF6FF7BACF5FF6FA3 + F2FF5592ECFF2F77E2FF1A67DAFF1C66D6FF1D65CFFF1B60C7FF1A5CBCFF1654 + AFFF2258A8BB3062AC7C1650A5FF1856B2FF195BBCFF1A5EC4FF1B62CBFF3F7E + DCFF76A6EFFF89B5F5FF8AB4F5FF88B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3 + F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3 + F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3 + F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF87B3F5FF89B4 + F5FF8BB5F5FF7EACF2FF4D88E1FF1F65CEFF1A5FC5FF1A5CBEFF1957B4FF134E + A6FF2F61ABBB5D83BB68134B9CFF1751A7FF1856B1FF1858B8FF3B76CEFF7BA8 + EBFF86B1F1FF82AEF0FF81AEF1FF81ADF0FF80ADF0FF7FADF0FF7FACF0FF7EAC + F0FF7EABF0FF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EAB + EFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EABEFFF7EAB + EFFF7EABEFFF7EABEFFF7EABF0FF7FACF0FF7FADF0FF80ADF0FF80ADF0FF81AE + F0FF82AEF1FF85B0F1FF82ADEFFF4A82D5FF1A5BBAFF1856B2FF1853A9FF0D46 + 9CFF597EB6A3A4B8D6272A5AA0FB0E4698FF1550A4FF1753ACFF225EB6FF2E68 + BFFF316BC3FF306BC5FF2E6BC5FF2B69C5FF2967C4FF2766C3FF2666C3FF2565 + C3FF2564C3FF2464C2FF2464C2FF2464C2FF2464C2FF2464C2FF2464C2FF2464 + C2FF2464C2FF2464C3FF2464C3FF2464C3FF2464C3FF2464C3FF2464C3FF2464 + C3FF2464C2FF2464C3FF2565C3FF2665C3FF2766C3FF2967C4FF2B68C4FF2D6A + C5FF2F6BC5FF306CC4FF2E69C1FF2560B8FF1855AEFF1750A6FF11489CFF174B + 99FFA2B5D250C8D3E4007495C56522549DF5134998FF154C9EFF0C479FFF0A46 + A0FF0D49A4FF0E4BA6FF0F4DA8FF104DA9FF104EAAFF104EAAFF104EAAFF114E + AAFF114EAAFF114FABFF114FACFF114FACFF114FACFF114FACFF114FACFF114F + ACFF114FACFF114FABFF114FABFF114FABFF114FABFF114FABFF114FABFF114F + ABFF114FABFF114EAAFF114EAAFF104EAAFF104EAAFF104EAAFF104DA9FF0F4D + A9FF0E4BA6FF0D4AA5FF0B47A1FF09459EFF144C9EFF124898FF1A4D99FF6085 + BC8BB0C2DD00C1CEE0008AA6D10084A2CF2B6A8DC170295CA8805279B3D0587F + BAF04471B4F03D6CB2F0396AB2F03768B2F03768B1F03667B1F03567B1F03567 + B1F03567B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466 + B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466B1F03466 + B1F03466B1F03566B1F03567B1F03567B1F03567B1F03667B1F03768B1F03969 + B1F03C6BB2F04270B4F0527BB8F06487BCE92659A5863F6DAF787092C64B88A5 + D000A7BCDA00C000000000030000800000000001000080000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000080000000000000008000000000010000E000 + 0000000700002800000020000000400000000100200000000000801000000000 + 000000000000000000000000000097A7BD026485B4703165B4B72965BDCC2D69 + C1ED2E6AC5F02E6CC8F02D6CC9F02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6C + CAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6CCAF02D6C + CAF02D6CCAF02D6CCAF02D6CCAF02D6CC9F02E6CC8F02E6BC6F02E69C2EF2B66 + BDD22F65B5BD6282B382AEB4BE0D6586B7591956B1FF155AC1FF1962CDFF1A65 + D5FF1B68D9FF1B69DCFF1B69DDFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6A + DEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6A + DEFF1B6ADEFF1B6ADEFF1B6ADEFF1B6ADEFF1B69DCFF1B68DAFF1B65D6FF1962 + CFFF165BC3FF1253B2FF6686B77D4371B5AC1255BAFF1C63CEFF1D69DAFF1F6D + E2FF1F6FE6FF2070E9FF2071EAFF2071EAFF2071EBFF2071EBFF2071EBFF2071 + EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071EBFF2071 + EBFF2071EBFF2071EBFF2071EAFF2071EAFF2070E9FF1F70E7FF1F6EE3FF1D6A + DBFF1D64D0FF1458BDFF3366B3C03467B4D6165BC3FF1D67D6FF1F6DE2FF2072 + EBFF2173EFFF2175F2FF2175F2FF2175F3FF2175F3FF2175F3FF2175F3FF2175 + F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175F3FF2175 + F3FF2175F3FF2175F3FF2175F3FF2175F2FF2174F2FF2173F0FF2072EBFF1F6E + E4FF1E68D8FF185EC6FF2860B5D4376BB9F4175DC7FF1E69DAFF1F70E7FF2173 + EFFF2176F3FF2177F6FF2077F7FF1C74F7FF1771F7FF1570F7FF1872F7FF1E75 + F7FF2278F7FF2278F7FF2278F8FF2278F8FF2278F8FF2278F7FF2177F7FF1D75 + F7FF1872F7FF1771F7FF1A73F7FF1E76F7FF2177F6FF2176F4FF2174F0FF1F70 + E8FF1E6ADCFF1860CBFF2F66BAEF3067B9F0185FCAFF1E6ADDFF2071E9FF2175 + F2FF2177F6FF1D75F8FF1972F8FF3785FAFF609EFAFF70A8FBFF5D9CFAFF3081 + FAFF1772FAFF2178FAFF2279FAFF2279FAFF2279FAFF1F77FAFF1872FAFF3483 + FAFF599AFAFF609EFAFF4B91FAFF267AF9FF1872F8FF2076F7FF2175F3FF2071 + EBFF1F6CDFFF1961CDFF2C65BBF02F66BAF01860CBFF1E6BDEFF2071EAFF2175 + F3FF1B73F7FF277BF9FF93BDFCFFEDF4FEFFFFFFFEFFFFFFFEFFFFFFFEFFDFEC + FDFF6DA6FCFF1B75FAFF1F77FAFF237AFAFF1D76FAFF2279FAFF83B4FCFFE8F0 + FEFFFEFEFEFFFFFFFEFFF9FBFEFFD1E2FDFF5F9DFAFF1972F7FF1F74F3FF2072 + ECFF1F6CE0FF1A62CFFF2A65BBF02E66BAF01860CBFF1E6BDEFF2071EBFF1C72 + F3FF2D7EF7FFC4DAFCFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFF + FEFFFFFFFEFF95BEFCFF1C75FAFF1C76FAFF287CFAFFB8D3FDFFFFFFFEFFFFFF + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFDFEFDFF86B4FAFF1971F3FF1F71 + ECFF1F6CE0FF1A62CFFF2A65BCF02E67BBF01860CBFF1E6BDEFF1F71EBFF1B71 + F3FFACCBF9FFFFFFFDFFFFFEFEFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFE + FEFFFFFFFEFFFFFFFEFF74ABFBFF106EFAFFA3C7FCFFFFFFFEFFFFFFFEFFFEFE + FEFFFFFFFEFFFFFFFEFFFFFFFEFFFEFEFEFFFFFFFDFFFFFEFCFF67A0F5FF166C + ECFF1F6CE0FF1A62CFFF2A64BCF02E67BBF01960CBFF1E6BDEFF186CEBFF4F91 + F3FFFBFBFBFFFFFFFDFFFEFEFEFFFFFFFEFFE5EFFDFFBDD6FDFFEEF4FEFFFFFF + FEFFFEFEFEFFFFFFFFFFD8E8FEFF5597FBFFF9FBFFFFFFFFFFFFFEFEFEFFFFFF + FEFFE9F1FDFFD5E5FDFFFEFEFEFFFFFFFEFFFDFDFEFFFFFFFCFFD1E0F7FF2474 + EDFF1C6BE0FF1A62CFFF2A64BCF02D66BBF01960CBFF1E6BDEFF1168EBFF86B2 + F5FFFFFFFBFFFEFDFDFFFFFFFEFFDCEAFDFF3D89FBFF1772FAFF4B92FBFFE9F2 + FEFFFFFFFFFFFFFFFFFFF7FBFFFFCEE1FEFFFFFFFFFFFFFFFFFFFFFFFFFFCCE0 + FDFF3685FBFF1E76FAFF76ABFCFFFAFCFEFFFFFFFEFFFEFDFCFFF6F7F8FF4A8C + EDFF1768E0FF1A62CFFF2A64BCF02D66BBF01960CCFF1E6BDFFF146AEBFF9ABE + F5FFFFFFFBFFFEFEFEFFFFFFFEFF96BFFCFF106EFAFF1F77FAFF1671FAFFB0CF + FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74AB + FCFF106EFAFF1D76FAFF1E76FAFFCADFFDFFFFFFFEFFFDFDFCFFFBFAF8FF629A + EEFF1466E0FF1A62CFFF2A65BCF02D66BBF01960CCFF1E6BDFFF1369EBFF98BD + F5FFFFFFFCFFFEFEFEFFFFFFFEFF9CC3FCFF1672FAFF2179FAFF1571FBFF90BC + FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71A8 + FDFF1672FAFF2078FAFF2077FAFFBED8FDFFFFFFFEFFFEFDFCFFF9F9F8FF5D97 + EEFF1566E1FF1A62CFFF2965BCF02D66BBF01960CCFF1E6BDFFF1169EBFF84B2 + F5FFFFFFFCFFFEFEFEFFFFFFFEFFC3DAFDFF1E76FAFF2178FAFF1D76FBFF3B88 + FCFFBAD5FEFFD9E8FEFFD9E8FEFFD9E8FEFFD9E8FEFFD9E8FEFFD9E8FEFF63A0 + FDFF1873FBFF2078FAFF1F77FAFFBDD7FDFFFFFFFEFFFFFEFCFFEFF3F8FF3C83 + EDFF1969E1FF1A62D0FF2965BCF02D66BBF01960CCFF1E6BDFFF166CEBFF5A98 + F5FFFEFDFCFFFFFFFEFFFFFFFEFFF7FAFEFF4C92FBFF1872FBFF237AFBFF1D76 + FBFF2078FBFF287DFBFF287DFBFF287DFBFF287DFBFF287DFBFF287DFBFF247A + FBFF2279FBFF1B75FAFF257BFAFFD8E7FEFFFFFFFEFFFFFFFCFFABC9F6FF1A6F + EDFF1D6CE1FF1A62D0FF2965BDF02D66BBF01960CCFF1E6BDFFF1C70EBFF2779 + F3FFD3E3FBFFFFFFFEFFFEFEFEFFFFFFFEFFC7DDFDFF257BFBFF1973FBFF2279 + FBFF2078FBFF1F77FBFF1F77FBFF1F77FBFF1F77FBFF1F77FBFF1F77FBFF2178 + FBFF1F77FBFF3283FAFFB4D1FDFFFFFFFEFFFEFFFEFFFFFFFCFF73A7F5FF126A + EDFF1F6CE1FF1A62D0FF2965BDF02D66BBF01960CCFF1E6BDFFF1F71EBFF1870 + F3FFB0CEFAFFFFFFFEFFFEFEFEFFFEFEFEFFFFFFFEFFC6DCFEFF418CFCFF1873 + FBFF1571FBFF1873FBFF1873FBFF1873FBFF1873FBFF1973FBFF1E76FBFF2279 + FBFF1F77FBFF2C7FFAFFD5E5FDFFFFFFFEFFFEFEFEFFFFFFFCFFB6D0F7FF1C70 + EDFF1D6CE1FF1A62D0FF2965BCF02D66BBF01960CCFF1E6BDFFF196EEBFF478C + F3FFF3F6FBFFFFFFFEFFFEFEFEFFFFFFFEFFEAF2FDFFD0E2FDFFB1D0FEFF89B7 + FDFF6FA7FDFF5F9EFCFF609FFCFF609FFCFF619FFCFF5E9DFCFF3584FCFF1D76 + FBFF2279FBFF1672FAFF63A0FBFFFEFEFEFFFFFFFEFFFFFFFCFFEAEFF8FF337E + EDFF1A6AE1FF1A62CFFF2965BCF02D66BBF01961CCFF1E6BDFFF1269EBFF82B0 + F5FFFFFFFBFFFEFEFEFFFFFFFEFFD7E7FDFF3A88FBFF1F77FAFF2078FAFFB6D3 + FEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDCEAFEFF3886 + FBFF1E77FAFF1F77FAFF2C7FFAFFE3EEFEFFFFFFFEFFFEFDFCFFF7F7F8FF5291 + EDFF1668E0FF1A62CFFF2965BCF02D66BBF01961CBFF1E6BDEFF1369EBFF98BE + F5FFFFFFFBFFFDFDFEFFFFFFFEFF96BFFCFF106EFAFF1F77FAFF1672FAFFA5C9 + FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69A4 + FBFF1772FAFF2078FAFF1E77FAFFBFD8FDFFFFFFFEFFFDFDFCFFFBFAF8FF639B + EEFF1466E0FF1A62CFFF2964BCF02D66BBF01960CBFF1E6BDEFF1269EBFF94BA + F5FFFFFFFBFFFDFDFDFFFFFFFEFFADCDFDFF136FFAFF1370FAFF1B75FAFFC6DC + FDFFFFFFFFFFFFFFFFFFFDFEFFFFF7FAFFFFFFFFFFFFFEFEFFFFFFFFFEFF82B3 + FCFF0D6CFAFF1772FAFF267BFAFFD9E8FEFFFFFFFEFFFDFDFCFFFBFAF8FF5F99 + EEFF1466E0FF1A62CFFF2A64BCF02D66BBF01860CBFF1E6BDEFF136AEBFF71A5 + F4FFFFFFFBFFFEFEFDFFFFFFFEFFFAFBFEFF91BCFCFF5498FBFFA0C5FCFFFEFE + FEFFFEFEFEFFFFFFFEFFECF4FEFFA1C6FDFFFFFFFEFFFEFEFEFFFFFFFEFFE4EE + FDFF64A1FBFF3F8AFBFFA6C9FCFFFFFFFEFFFEFEFDFFFFFEFCFFF2F4F8FF4085 + EDFF1869E0FF1A62CFFF2A64BCF02D65BAF01860CBFF1E6BDEFF1B6FEBFF317F + F3FFE4EDFBFFFFFFFDFFFEFEFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFF + FEFFFEFEFEFFFFFFFEFFB0CEFDFF3383FAFFEEF4FEFFFFFFFEFFFEFEFEFFFFFF + FEFFFFFFFEFFF8FAFEFFFFFFFEFFFFFEFEFFFDFDFDFFFFFFFCFFBBD3F7FF1D70 + ECFF1D6CE0FF1A62CFFF2A64BCF02E66BAF01860CBFF1E6BDEFF2071EAFF166F + F3FF6BA4F8FFFEFEFDFFFFFFFEFFFEFEFEFFFFFEFEFFFFFFFEFFFFFEFEFFFFFF + FEFFFFFFFEFFE6F0FEFF3F8BFBFF106EFAFF80B2FCFFFFFFFEFFFFFFFEFFFEFE + FEFFFFFFFEFFFFFFFEFFFFFEFEFFFEFEFEFFFFFFFDFFEEF3FBFF488DF4FF186D + ECFF1F6CE0FF1A62CEFF2A64BBF02E66B9F0185FC9FF1E6ADDFF2071EAFF2074 + F1FF1871F5FF72A8F9FFEAF2FCFFFFFFFDFFFFFFFDFFFFFFFDFFFFFFFEFFFFFF + FEFFD1E2FDFF4A91FBFF1873F9FF2178FAFF1C75FAFF8BB8FCFFF9FBFEFFFFFF + FEFFFFFFFDFFFFFFFDFFFFFFFDFFFFFFFDFFE1ECFCFF5798F8FF166EF2FF2071 + EBFF1F6CDFFF1A62CDFF2A64BAF02E65B8F0185EC8FF1E69DBFF1F70E8FF2174 + F0FF2075F4FF1671F6FF3D88F7FF83B2F9FFBDD5FAFFD1E2FBFFB8D3FAFF74A9 + F9FF297DF8FF1972F7FF2278F8FF2278F8FF1F76F8FF1872F7FF5093F9FFABCB + FAFFE7EFFBFFF1F5FBFFD2E2FBFF8BB8F9FF3683F6FF1770F4FF2174F1FF1F70 + E9FF1E6ADDFF1960CBFF2A64B9F02F65B5F0175DC4FF1E68D7FF1F6EE3FF2072 + ECFF2074F0FF1F74F2FF1970F3FF116CF3FF1C72F3FF2276F3FF1A71F3FF116C + F3FF1A71F4FF1E74F4FF1D74F4FF1D74F4FF1D74F4FF1D73F4FF146EF3FF176F + F3FF297AF3FF2E7DF3FF2276F3FF136DF3FF1A71F2FF2173F1FF2072EDFF1F6F + E5FF1E69D9FF185FC7FF2B63B6F13769B3F11558BDFF1C64D0FF1D6ADCFF1C6C + E3FF1B6DE7FF2474EBFF2C7AEEFF317DEFFF317EEFFF327EF0FF337FF0FF3580 + F0FF3580F0FF3580F0FF3580F0FF3580F0FF3580F0FF3580F0FF3580F0FF347F + F0FF317EF0FF307CEFFF2F7CEFFF2D7AEEFF2575ECFF1C6EE8FF1C6DE4FF1E6B + DDFF1D65D2FF175BC0FF2D63B2EE2B5FAACF1554B3FF1B5FC5FF1A63D0FF2A71 + DCFF5793EBFF76A8F3FF80AFF6FF83B1F6FF84B1F6FF84B2F6FF84B2F6FF84B2 + F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2F6FF84B2 + F6FF84B2F6FF84B1F6FF83B1F6FF81AFF6FF78AAF4FF5D97EDFF2F75DEFF1A63 + D1FF1B60C7FF1857B6FF1F57A8CE3A68ADA6104BA3FF1656B4FF2A69C8FF6E9F + E8FF87B1F2FF83AFF1FF81ADF1FF80ADF0FF7FACF0FF7FACF0FF7FACF0FF7FAC + F0FF7EACF0FF7FACF0FF7FACF0FF7FACF0FF7FACF0FF7EACF0FF7FACF0FF7FAC + F0FF7FACF0FF7FACF0FF80ADF0FF81ADF1FF83AFF1FF87B2F2FF76A5EBFF3270 + CCFF1656B6FF104BA5FF3A68ACC28EA7CC50184C9AFF0E489EFF1A55ADFF2661 + B9FF2763BDFF2663BDFF2361BDFF2361BEFF2260BDFF2160BDFF2160BDFF2160 + BDFF2160BDFF2160BDFF2160BDFF205FBDFF205FBCFF205FBCFF205FBDFF205F + BDFF205FBDFF205FBDFF2160BDFF2462BEFF2664BEFF2964BDFF2963BAFF1D58 + AFFF104AA1FF0F4698FF819CC66FB7C7DE006E90C3523B69ADAC2B5BA5D43363 + ABF0275CAAF0255BAAF0245AABF01C54A7F01C54A7F01C54A7F01C54A7F01C54 + A7F01C54A7F01C54A7F01C54A8F0235AABF0235AABF0235AABF0235AABF0235A + ABF0245AABF0245AABF0245AABF01C54A7F01D54A6F01D53A4F02156A4F02154 + A1E63362A9C25D83BC5F9EB5D701000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000080000000} + PopupMenu = puSystray + Visible = True + OnDblClick = TrayIcon1DblClick + Left = 12 + Top = 224 + end + object puSystray: TPopupMenu + Left = 76 + Top = 224 + object miShowHide: TMenuItem + Caption = 'Show / Hide' + Default = True + OnClick = miShowHideClick + end + object N1: TMenuItem + Caption = '-' + end + object miTerminate: TMenuItem + Caption = 'Quit' + OnClick = miTerminateClick + end + end + object ApplicationEvents1: TApplicationEvents + OnException = ApplicationEvents1Exception + Left = 12 + Top = 256 + end + object ImageList: TImageList + Masked = False + Left = 380 + Top = 8 + Bitmap = { + 494C010103003000D80010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 + 0000000000003600000028000000400000001000000001002000000000000010 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000008080800080808000808080008080800080808000808080000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000800000008000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000646464006464640000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000008080 + 8000808080000000800000008000000080000000800000008000000080008080 + 8000808080000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000008000 + 0000008000000080000080000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000006464 + 6400A6A6A600A6A6A60064646400000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000808080000000 + 800000008000000080000000FF000000FF000000FF000000FF00000080000000 + 8000000080008080800000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000800000000080 + 0000008000000080000000800000800000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000064646400A6A6 + A600A6A6A600A6A6A600A6A6A600646464000000000000000000000000000000 + 000000000000000000000000000000000000000000000000FF00000080000000 + 80000000FF000000FF0000000000000000000000000000000000000080000000 + 8000000080000000800080808000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000080000000008000000080 + 0000008000000080000000800000008000008000000000000000000000000000 + 0000000000000000000000000000000000000000000064646400A6A6A600A6A6 + A600A6A6A600A6A6A600A6A6A600A6A6A6006464640000000000000000000000 + 000000000000000000000000000000000000000000000000FF00000080000000 + 8000000000000000000000000000000000000000000080808000000080000000 + 8000000080000000800080808000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000008000000000800000008000000080 + 000000FF00000080000000800000008000000080000080000000000000000000 + 00000000000000000000000000000000000064646400A6A6A600A6A6A600A6A6 + A600F0F0F000A6A6A600A6A6A600A6A6A600A6A6A60064646400000000000000 + 0000000000000000000000000000000000000000FF0000008000000080008080 + 8000000000000000000000000000000000008080800000008000000080000000 + 80000000FF000000800000008000808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000800000008000000080000000FF + 00000000000000FF000000800000008000000080000080000000000000000000 + 000000000000000000000000000000000000A6A6A600A6A6A600A6A6A600F0F0 + F00000000000F0F0F000A6A6A600A6A6A600A6A6A60064646400000000000000 + 0000000000000000000000000000000000000000FF0000008000808080000000 + 0000000000000000000000000000808080000000800000008000000080000000 + FF00000000000000FF0000008000808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000FF00000080000000FF00000000 + 0000000000000000000000FF0000008000000080000000800000800000000000 + 000000000000000000000000000000000000F0F0F000A6A6A600F0F0F0000000 + 00000000000000000000F0F0F000A6A6A600A6A6A600A6A6A600646464000000 + 0000000000000000000000000000000000000000FF0000008000808080000000 + 00000000000000000000808080000000800000008000000080000000FF000000 + 0000000000000000FF0000008000808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000FF0000000000000000 + 000000000000000000000000000000FF00000080000000800000008000008000 + 00000000000000000000000000000000000000000000F0F0F000000000000000 + 0000000000000000000000000000F0F0F000A6A6A600A6A6A600A6A6A6006464 + 6400000000000000000000000000000000000000FF0000008000808080000000 + 000000000000808080000000800000008000000080000000FF00000000000000 + 0000000000000000FF0000008000808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000FF000000800000008000000080 + 0000800000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000F0F0F000A6A6A600A6A6A600A6A6 + A600646464000000000000000000000000000000FF0000008000808080000000 + 0000808080000000800000008000000080000000FF0000000000000000000000 + 0000000000000000FF0000008000808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000FF0000008000000080 + 0000008000008000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000F0F0F000A6A6A600A6A6 + A600A6A6A6006464640000000000000000000000FF0000008000000080008080 + 80000000800000008000000080000000FF000000000000000000000000000000 + 00000000FF000000800000008000808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000FF00000080 + 0000008000000080000080000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000F0F0F000A6A6 + A600A6A6A600A6A6A6006464640000000000000000000000FF00000080000000 + 800000008000000080000000FF00000000000000000000000000000000000000 + 00000000FF000000800080808000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000FF + 0000008000000080000000800000800000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000F0F0 + F000A6A6A600A6A6A600A6A6A60064646400000000000000FF00000080000000 + 8000000080008080800000000000000000000000000000000000808080000000 + 8000000080000000800080808000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000FF00000080000000800000800000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000F0F0F000A6A6A600A6A6A6006464640000000000000000000000FF000000 + 8000000080000000800080808000808080008080800080808000000080000000 + 8000000080008080800000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000FF000000800000008000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000F0F0F000A6A6A600A6A6A6000000000000000000000000000000 + FF000000FF000000800000008000000080000000800000008000000080000000 + FF000000FF000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000FF0000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000F0F0F000000000000000000000000000000000000000 + 0000000000000000FF000000FF000000FF000000FF000000FF000000FF000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000424D3E000000000000003E000000 + 2800000040000000100000000100010000000000800000000000000000000000 + 000000000000000000000000FFFFFF00FFFFFFFFF81F0000F3FFF3FFE0070000 + E1FFE1FFC0030000C0FFC0FF83C10000807F807F8F810000003F003F0F000000 + 083F083F1E0800001C1F1C1F1C180000BE0FBE0F18380000FF07FF0710780000 + FF83FF8300F00000FFC1FFC181F10000FFE0FFE083C10000FFF0FFF0C0030000 + FFF8FFF8E0070000FFFDFFFDF81F000000000000000000000000000000000000 + 000000000000} + end + object puGeneral: TPopupMenu + Left = 320 + Top = 8 + end + object puTomcatAction: TPopupMenu + Left = 448 + Top = 8 + object miActionTomcatAuto: TMenuItem + Caption = 'Start' + OnClick = miActionTomcatAutoClick + end + object N2: TMenuItem + Caption = '-' + end + object Statuserkennungnichtsicher1: TMenuItem + Caption = '[ Detection of status not trusty ]' + Enabled = False + end + object miActionTomcatStart: TMenuItem + Caption = 'Force start' + OnClick = miActionTomcatStartClick + end + object miActionTomcatStop: TMenuItem + Caption = 'Force stop' + OnClick = miActionTomcatStopClick + end + end +end diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMain.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMain.pas new file mode 100755 index 0000000000..ec9fab6249 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMain.pas @@ -0,0 +1,909 @@ +(* +This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. +To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or +send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. + +Programmed by Steffen Strueber, + +Updates: +3.0.2: May 10th 2011, Steffen Strueber +*) + +unit uMain; + +interface + +uses + GnuGettext, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, ExtCtrls, ComCtrls, Buttons, uTools, uTomcat, + uApache, uMySQL, uFileZilla, uMercury, uNetstat, uNetstatTable, Menus, + IniFiles, uProcesses, AppEvnts, ImgList, JCLDebug ; + + +type + TfMain = class(TForm) + imgXAMPP: TImage; + lHeader: TLabel; + bConfig: TBitBtn; + bSCM: TBitBtn; + gbModules: TGroupBox; + bApacheAction: TBitBtn; + bApacheAdmin: TBitBtn; + bMySQLAction: TBitBtn; + bMySQLAdmin: TBitBtn; + bFileZillaAction: TBitBtn; + bFileZillaAdmin: TBitBtn; + bMercuryAction: TBitBtn; + bMercuryAdmin: TBitBtn; + sbMain: TStatusBar; + bQuit: TBitBtn; + bHelp: TBitBtn; + bExplorer: TBitBtn; + pApacheStatus: TPanel; + TimerUpdateStatus: TTimer; + TrayIcon1: TTrayIcon; + bNetstat: TBitBtn; + puSystray: TPopupMenu; + miShowHide: TMenuItem; + miTerminate: TMenuItem; + N1: TMenuItem; + bApacheConfig: TBitBtn; + lPIDs: TLabel; + lPorts: TLabel; + lApachePIDs: TLabel; + bApacheLogs: TBitBtn; + lApachePorts: TLabel; + bMySQLConfig: TBitBtn; + bMySQLLogs: TBitBtn; + bFileZillaConfig: TBitBtn; + bFileZillaLogs: TBitBtn; + bMercuryConfig: TBitBtn; + reLog: TRichEdit; + lMySQLPIDs: TLabel; + lMySQLPorts: TLabel; + lFileZillaPorts: TLabel; + lFileZillaPIDs: TLabel; + lMercuryPorts: TLabel; + lMercuryPIDs: TLabel; + pMySQLStatus: TPanel; + pFileZillaStatus: TPanel; + pMercuryStatus: TPanel; + ApplicationEvents1: TApplicationEvents; + ImageList: TImageList; + bMySQLService: TBitBtn; + bFileZillaService: TBitBtn; + Label1: TLabel; + Label2: TLabel; + Label3: TLabel; + bApacheService: TBitBtn; + bMercurylogs: TBitBtn; + puGeneral: TPopupMenu; + lTomcatPorts: TLabel; + lTomcatPIDs: TLabel; + bTomcatAction: TBitBtn; + bTomcatAdmin: TBitBtn; + bTomcatConfig: TBitBtn; + pTomcatStatus: TPanel; + bTomcatLogs: TBitBtn; + bTomcatService: TBitBtn; + bMercuryService: TBitBtn; + bXamppShell: TBitBtn; + puTomcatAction: TPopupMenu; + miActionTomcatAuto: TMenuItem; + N2: TMenuItem; + Statuserkennungnichtsicher1: TMenuItem; + miActionTomcatStop: TMenuItem; + miActionTomcatStart: TMenuItem; + procedure FormCreate(Sender: TObject); + procedure bApacheActionClick(Sender: TObject); + procedure TimerUpdateStatusTimer(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure bNetstatClick(Sender: TObject); + procedure miTerminateClick(Sender: TObject); + procedure bQuitClick(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); + procedure miShowHideClick(Sender: TObject); + procedure TrayIcon1DblClick(Sender: TObject); + procedure bExplorerClick(Sender: TObject); + procedure bSCMClick(Sender: TObject); + procedure bApacheAdminClick(Sender: TObject); + procedure bApacheConfigClick(Sender: TObject); + procedure miGeneralClick(Sender: TObject); + procedure bConfigClick(Sender: TObject); + procedure bApacheLogsClick(Sender: TObject); + procedure miApacheLogsAccessClick(Sender: TObject); + procedure miApacheLogsErrorClick(Sender: TObject); + procedure bMySQLActionClick(Sender: TObject); + procedure bMySQLAdminClick(Sender: TObject); + procedure bMySQLConfigClick(Sender: TObject); + procedure bMySQLLogsClick(Sender: TObject); + procedure bFileZillaActionClick(Sender: TObject); + procedure bFileZillaAdminClick(Sender: TObject); + procedure bFileZillaConfigClick(Sender: TObject); + procedure bFileZillaLogsClick(Sender: TObject); + procedure bMercuryActionClick(Sender: TObject); + procedure bMercuryAdminClick(Sender: TObject); + procedure bMercuryConfigClick(Sender: TObject); + procedure bHelpClick(Sender: TObject); + procedure bApacheServiceClick(Sender: TObject); + procedure bMySQLServiceClick(Sender: TObject); + procedure bFileZillaServiceClick(Sender: TObject); + procedure bMercuryServiceClick(Sender: TObject); + procedure bMercurylogsClick(Sender: TObject); + procedure bXamppShellClick(Sender: TObject); + procedure bTomcatConfigClick(Sender: TObject); + procedure bTomcatLogsClick(Sender: TObject); + procedure bTomcatActionClick(Sender: TObject); + procedure bTomcatAdminClick(Sender: TObject); + procedure miActionTomcatAutoClick(Sender: TObject); + procedure miActionTomcatStopClick(Sender: TObject); + procedure miActionTomcatStartClick(Sender: TObject); + procedure ApplicationEvents1Exception(Sender: TObject; E: Exception); + private + Apache: tApache; + MySQL: tMySQL; + FileZilla: tFileZilla; + Mercury: tMercury; + Tomcat: tTomcat; + WindowsShutdownInProgress: boolean; + procedure UpdateStatusAll; + function TryGuessXamppVersion:string; + procedure EditConfigLogs(ConfigFile: string); + procedure GeneralPUClear; + procedure GeneralPUAdd(text: string=''; hint: string=''; tag: integer=0); + procedure GeneralPUAddUser(text: string; hint: string=''); + procedure GeneralPUAddUserFromSL(sl: tStringList); + procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QueryEndSession; // detect Windows shutdown message + procedure SaveLogFile; + public + procedure AddLog(module,log: string; LogType: tLogType=ltDefault); overload; + procedure AddLog(log: string; LogType: tLogType=ltDefault); overload; + end; + +var + fMain: TfMain; + +implementation + +uses uConfig, uHelp; + +{$R *.dfm} + +procedure TfMain.miShowHideClick(Sender: TObject); +begin + if Visible then begin + Hide; + if fMain.WindowState=wsMinimized then fMain.WindowState:=wsNormal; + end else begin + Show; + if fMain.WindowState=wsMinimized then fMain.WindowState:=wsNormal; + Application.BringToFront; + end; +end; + + +procedure TfMain.miGeneralClick(Sender: TObject); +var + mi: TMenuItem; + App: string; +begin + if not (Sender is TMenuItem) then exit; + mi:=Sender as tMenuItem; + if mi.Tag=0 then + EditConfigLogs(mi.Hint); + if mi.Tag=1 then begin + App:=BaseDir+mi.Hint; + ExecuteFile(App,'','',SW_SHOW); + Addlog(Format(_('Executing "%s"'),[App])); + end; +end; + +procedure TfMain.AddLog(module, log: string; LogType: tLogType=ltDefault); +begin + if (not Config.ShowDebug) and (LogType=ltDebug) or (LogType=ltDebugDetails) then exit; + if (LogType=ltDebugDetails) and (Config.DebugLevel=0) then exit; + + with reLog do begin + SelStart := GetTextLen; + + SelAttributes.Color := clGray; + SelText := TimeToStr(Now)+' '; + + SelAttributes.Color := clBlack; + SelText := '['; + + SelAttributes.Color := clBlue; + SelText := module; + + SelAttributes.Color := clBlack; + SelText := '] '+#9; + + + case logtype of + ltDefault: SelAttributes.Color := clBlack; + ltInfo: SelAttributes.Color := clBlue; + ltError: SelAttributes.Color := clRed; + ltDebug: SelAttributes.Color := clGray; + ltDebugDetails: SelAttributes.Color := clSilver; + end; + + SelText := Log+#13; + +// SelStart := GetTextLen; + SendMessage(Handle, EM_SCROLLCARET,0,0); + end; + +end; + +procedure TfMain.AddLog(log: string; LogType: tLogType=ltDefault); +begin + AddLog('main',log,LogType); +end; + +procedure TfMain.ApplicationEvents1Exception(Sender: TObject; E: Exception); +var ts:tSTringList; + i:integer; +begin +// GlobalAddLog(Format('Exception in thread: %d / %s', [Thread.ThreadID, JclDebugThreadList.ThreadClassNames[Thread.ThreadID]]),0,'LogException'); + // Note: JclLastExceptStackList always returns list for *current* thread ID. To simplify getting the + // stack of thread where an exception occured JclLastExceptStackList returns stack of the thread instead + // of current thread when called *within* the JclDebugThreadList.OnSyncException handler. This is the + // *only* exception to the behavior of JclLastExceptStackList described above. + ts:=TStringList.Create; + + AddLog('EXCEPTION',E.Message,ltError); + + JclLastExceptStackList.AddToStrings(ts, True, True, True); + for i:=0 to ts.count-1 do + AddLog('EXCEPTION',ts[i],ltError); + ts.Free; +end; + +procedure TfMain.miActionTomcatAutoClick(Sender: TObject); +begin + if Tomcat.isRunning then Tomcat.Stop + else Tomcat.Start; +end; + +procedure TfMain.miActionTomcatStartClick(Sender: TObject); +begin + Tomcat.Start; +end; + +procedure TfMain.miActionTomcatStopClick(Sender: TObject); +begin + Tomcat.Stop +end; + +procedure TfMain.bApacheActionClick(Sender: TObject); +begin + if Apache.isRunning then Apache.Stop + else Apache.Start; +end; + +procedure TfMain.miTerminateClick(Sender: TObject); +begin + Application.Terminate; +end; + + +procedure TfMain.SaveLogFile; +var + en: string; + LogFileName: string; + f: TextFile; + i: Integer; +begin + en:=ExtractFileName(Application.ExeName); + while (length(en)>0) and (en[length(en)]<>'.') do en:=copy(en,1,length(en)-1); + LogFileName:=Basedir+en+'log'; + AssignFile(f,LogFileName); + if FileExists(LogFileName) then Append(f) + else Rewrite(f); + for i:=0 to reLog.Lines.Count-1 do + Writeln(f,reLog.Lines[i]); + Writeln(f,''); + CloseFile(f); +end; + +procedure TfMain.miApacheLogsAccessClick(Sender: TObject); +begin + Apache.ShowLogs(altAccess); +end; + +procedure TfMain.miApacheLogsErrorClick(Sender: TObject); +begin + Apache.ShowLogs(altError); +end; + +procedure TfMain.bQuitClick(Sender: TObject); +begin + miTerminateClick(Sender); +end; + +procedure TfMain.bExplorerClick(Sender: TObject); +var + App: string; +begin + App:=BaseDir; + ExecuteFile(App,'','',SW_SHOW); + Addlog(Format(_('Executing "%s"'),[App])); +end; + +procedure TfMain.bFileZillaActionClick(Sender: TObject); +begin + if FileZilla.isService then begin + if FileZilla.isRunning then FileZilla.Stop + else FileZilla.Start; + end else begin + MessageDlg(_('FileZilla must be run as service!'),mtInformation,[mbOK],0); + end; +end; + +procedure TfMain.bFileZillaAdminClick(Sender: TObject); +begin + FileZilla.Admin; +end; + +procedure TfMain.bFileZillaConfigClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('FileZilla Server.xml','FileZillaFTP\FileZilla Server.xml'); + GeneralPUAddUserFromSL(Config.UserConfig.FileZilla); + GeneralPUAdd(); + GeneralPUAdd(_(''),'FileZillaFTP',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bFileZillaLogsClick(Sender: TObject); +begin + GeneralPUClear; +// GeneralPUAdd('error.log','mysql\data\mysql_error.log'); + GeneralPUAddUserFromSL(Config.UserLogs.FileZilla); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bFileZillaServiceClick(Sender: TObject); +var oldIsService: boolean; +begin + oldIsService:=FileZilla.isService; + + if FileZilla.isRunning then begin + MessageDlg(_('Services cant be installed or uninstalled while service is running!'),mtError,[mbOk],0); + exit; + end; + if FileZilla.isService then begin + if MessageDlg(Format(_('Click Yes to uninstall the %s service'),[FileZilla.ModuleName]),mtConfirmation,[mbYes, mbNo],0)=mrYes then + FileZilla.ServiceUnInstall + else + exit; + end else begin + if MessageDlg(Format(_('Click Yes to install the "%s" service'),[FileZilla.ModuleName]),mtConfirmation,[mbYes, mbNo],0)=mrYes then + FileZilla.ServiceInstall + else + exit; + end; + FileZilla.CheckIsService; + if (oldIsService=FileZilla.isService) then begin + FileZilla.AddLog(_('Service was NOT (un)installed!'),ltError); + if (WinVersion.Major=5) then // WinXP + FileZilla.AddLog(_('One possible reason for failure: On windows security box you !!!MUST UNCHECK!!! that "Protect my computer and data from unauthorized program activity" checkbox!!!'),ltError); + end; +end; + +procedure TfMain.bHelpClick(Sender: TObject); +begin + fHelp.Show; +end; + +procedure TfMain.bApacheServiceClick(Sender: TObject); +var oldIsService: boolean; +begin + oldIsService:=Apache.isService; + + if Apache.isRunning then begin + MessageDlg(_('Services cant be installed or uninstalled while service is running!'),mtError,[mbOk],0); + exit; + end; + + if Apache.isService then begin + if MessageDlg(Format(_('Click Yes to uninstall the %s service'),[Apache.ModuleName]),mtConfirmation,[mbYes, mbNo],0)=mrYes then + Apache.ServiceUnInstall + else + exit; + end else begin + if MessageDlg(Format(_('Click Yes to install the "%s" service'),[Apache.ModuleName]),mtConfirmation,[mbYes, mbNo],0)=mrYes then + Apache.ServiceInstall + else + exit; + end; + Apache.CheckIsService; + if (oldIsService=Apache.isService) then begin + Apache.AddLog(_('Service was NOT (un)installed!'),ltError); + if (WinVersion.Major=5) then // WinXP + Apache.AddLog(_('One possible reason for failure: On windows security box you !!!MUST UNCHECK!!! that "Protect my computer and data from unauthorized program activity" checkbox!!!'),ltError); + end else begin + Apache.AddLog(_('Successful!')); + end; +end; + +procedure TfMain.bMercuryActionClick(Sender: TObject); +begin + if Mercury.isRunning then Mercury.Stop + else Mercury.Start; +end; + +procedure TfMain.bMercuryAdminClick(Sender: TObject); +begin + Mercury.Admin; +end; + +procedure TfMain.bMercuryConfigClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('mercury.ini','MercuryMail\mercury.ini'); + GeneralPUAddUserFromSL(Config.UserConfig.Mercury); + GeneralPUAdd(); + GeneralPUAdd(_(''),'MercuryMail',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bMercurylogsClick(Sender: TObject); +begin + GeneralPUClear; +// GeneralPUAdd('error.log','mysql\data\mysql_error.log'); + GeneralPUAddUserFromSL(Config.UserLogs.Mercury); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bMercuryServiceClick(Sender: TObject); +begin + MessageDlg(_('Mercury cant be run as service!'),mtError,[mbOk],0); +end; + +procedure TfMain.bMySQLActionClick(Sender: TObject); +begin + if MySQL.isRunning then MySQL.Stop + else MySQL.Start; +end; + +procedure TfMain.bMySQLAdminClick(Sender: TObject); +begin + MySQL.Admin; +end; + +procedure TfMain.bMySQLConfigClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('my.ini','mysql\bin\my.ini'); + GeneralPUAddUserFromSL(Config.UserConfig.MySQL); + GeneralPUAdd(); + GeneralPUAdd(_(''),'mysql',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bMySQLLogsClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('error.log','mysql\data\mysql_error.log'); + GeneralPUAddUserFromSL(Config.UserLogs.MySQL); + GeneralPUAdd(); + GeneralPUAdd(_(''),'mysql\data',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bMySQLServiceClick(Sender: TObject); +var oldIsService: boolean; +begin + oldIsService:=Apache.isService; + + if MySQL.isRunning then begin + MessageDlg(_('Services cant be installed or uninstalled while service is running!'),mtError,[mbOk],0); + exit; + end; + if MySQL.isService then begin + if MessageDlg(Format(_('Click Yes to uninstall the %s service'),[MySQL.ModuleName]),mtConfirmation,[mbYes, mbNo],0)=mrYes then + MySQL.ServiceUnInstall + else + exit; + end else begin + if MessageDlg(Format(_('Click Yes to install the "%s" service'),[MySQL.ModuleName]),mtConfirmation,[mbYes, mbNo],0)=mrYes then + MySQL.ServiceInstall + else + exit; + end; + MySQL.CheckIsService; + if (oldIsService=MySQL.isService) then begin + MySQL.AddLog(_('Service was NOT (un)installed!'),ltError); + if (WinVersion.Major=5) then // WinXP + MySQL.AddLog(_('One possible reason for failure: On windows security box you !!!MUST UNCHECK!!! that "Protect my computer and data from unauthorized program activity" checkbox!!!'),ltError); + end; +end; + +procedure TfMain.bConfigClick(Sender: TObject); +begin + fConfig.Show; +end; + +procedure TfMain.bSCMClick(Sender: TObject); +var + App: string; +begin + App:='services.msc'; + ExecuteFile(App,'','',SW_SHOW); + Addlog(Format(_('Executing "%s"'),[App])); +end; + +procedure TfMain.bTomcatActionClick(Sender: TObject); +begin + if Tomcat.isRunning then begin + miActionTomcatAuto.Caption:='Stop'; + miActionTomcatStop.Visible:=false; + miActionTomcatStart.Visible:=true; + end else begin + miActionTomcatAuto.Caption:='Start'; + miActionTomcatStop.Visible:=true; + miActionTomcatStart.Visible:=false; + end; + + puTomcatAction.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +// if Tomcat.isRunning then Tomcat.Stop +// else Tomcat.Start; +end; + +procedure TfMain.bTomcatAdminClick(Sender: TObject); +begin + Tomcat.Admin; +end; + +procedure TfMain.bTomcatConfigClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('server.xml','Tomcat\conf\server.xml'); + GeneralPUAdd('tomcat-users.xml','Tomcat\conf\tomcat-users.xml'); + GeneralPUAdd('web.xml','Tomcat\conf\web.xml'); + GeneralPUAdd('context.xml','Tomcat\conf\context.xml'); + GeneralPUAddUserFromSL(Config.UserConfig.Tomcat); + GeneralPUAdd(); + GeneralPUAdd(_(''),'Tomcat\conf',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bTomcatLogsClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAddUserFromSL(Config.UserLogs.Tomcat); + GeneralPUAdd(); + GeneralPUAdd(_(''),'tomcat\logs',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bXamppShellClick(Sender: TObject); +const + cBatchFileContents= + '@ECHO OFF'+cr+ + ''+cr+ + 'GOTO weiter'+cr+ + ':setenv'+cr+ + 'SET "MIBDIRS=%~dp0php\extras\mibs"'+cr+ + 'SET "MIBDIRS=%MIBDIRS:\=/%"'+cr+ + 'SET "MYSQL_HOME=%~dp0mysql\bin"'+cr+ + 'SET "OPENSSL_CONF=%~dp0apache\bin\openssl.cnf"'+cr+ + 'SET "OPENSSL_CONF=%OPENSSL_CONF:\=/%"'+cr+ + 'SET "PHP_PEAR_SYSCONF_DIR=%~dp0php"'+cr+ + 'SET "PHPRC=%~dp0php"'+cr+ + 'SET "TMP=%~dp0tmp"'+cr+ + 'SET "PERL5LIB="'+cr+ + 'SET "Path=%~dp0;%~dp0php;%~dp0perl\site\bin;%~dp0perl\bin;%~dp0apache\bin;%~dp0mysql\bin;%~dp0FileZillaFTP;%~dp0MercuryMail;%~dp0sendmail;%~dp0webalizer;%~dp0tomcat\bin;%Path%"'+cr+ + 'GOTO :EOF'+cr+ + ':weiter'+cr+ + ''+cr+ + 'IF "%1" EQU "setenv" ('+cr+ + ' ECHO.'+cr+ + ' ECHO Setting environment for using XAMPP for Windows.'+cr+ + ' CALL :setenv'+cr+ + ') ELSE ('+cr+ + ' SETLOCAL'+cr+ + ' TITLE XAMPP for Windows'+cr+ + ' PROMPT %username%@%computername%$S$P$_#$S'+cr+ + ' START "" /B %COMSPEC% /K "%~f0" setenv'+cr+ + ')'; + cFilename = 'xampp_shell.bat'; +var + ts: TStringList; + batchfile: string; +begin + batchfile:=BaseDir+cFilename; + if not fileexists(BatchFile) then begin + if MessageDlg(Format(_('File "%s" not found. Should it be created now?'),[BatchFile]),mtConfirmation,[mbYes, mbAbort],0)<>mrYes then exit; + ts:=TStringList.Create; + ts.Text:=cBatchFileContents; + try + ts.SaveToFile(batchfile); + except + on e:exception do begin + MessageDlg(_('Error')+': '+E.Message,mtError,[mbOK],0); + end; + end; + ts.Free; + end; + ExecuteFile(batchfile,'','',SW_SHOW); +end; + +procedure TfMain.GeneralPUAdd(text: string=''; hint: string=''; tag: integer=0); +var + mi: TMenuItem; +begin + mi:=TMenuItem.Create(puGeneral); + mi.Caption:=text; + if text='' then begin + mi.Caption:='-'; + end else begin + mi.Hint:=hint; + mi.Tag:=tag; + mi.OnClick:=miGeneralClick; + end; + puGeneral.Items.Add(mi); +end; + +procedure TfMain.GeneralPUAddUser(text: string; hint: string=''); +var + myCaption: TranslatedUnicodeString; + mi, miMain: TMenuItem; +begin + myCaption:=_('User defined'); + miMain:=puGeneral.Items.Find(myCaption); + if miMain=nil then begin + GeneralPUAdd(); + miMain:=TMenuItem.Create(puGeneral); + miMain.Caption:=myCaption; + puGeneral.Items.Add(miMain); + end; + + mi:=TMenuItem.Create(miMain); + mi.Caption:=text; + if hint<>'' then mi.Hint:=hint + else mi.Hint:=text; + mi.OnClick:=miGeneralClick; + miMain.Add(mi); +end; + +procedure TfMain.GeneralPUAddUserFromSL(sl: tStringList); +var + i: Integer; +begin + for i:=0 to sl.Count-1 do + GeneralPUAddUser(sl[i]); +end; + +procedure TfMain.GeneralPUClear; +begin + puGeneral.Items.Clear; +end; + +procedure TfMain.EditConfigLogs(ConfigFile: string); +var + App, Param: string; +begin + App:=Config.EditorApp; + Param:=BaseDir+ConfigFile; + Addlog(Format(_('Executing %s %s'),[App,Param]),ltDebug); + ExecuteFile(app,param,'',SW_SHOW); +end; + +procedure TfMain.bNetstatClick(Sender: TObject); +begin + fNetStat.Show; + fNetstat.RefreshTable(true); +end; + +procedure TfMain.bApacheAdminClick(Sender: TObject); +begin + Apache.Admin; +end; + +procedure TfMain.bApacheConfigClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('Apache (httpd.conf)','apache/conf/httpd.conf'); + GeneralPUAdd('PHP (php.ini)','php/php.ini'); + GeneralPUAddUserFromSL(Config.UserConfig.Apache); + GeneralPUAdd(); + GeneralPUAdd(_('')+' [Apache]','apache',1); + GeneralPUAdd(_('')+' [PHP]','php',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.bApacheLogsClick(Sender: TObject); +begin + GeneralPUClear; + GeneralPUAdd('access.log','apache\logs\access.log'); + GeneralPUAdd('error.log','apache\logs\access.log'); + GeneralPUAddUserFromSL(Config.UserLogs.Apache); + GeneralPUAdd(); + GeneralPUAdd(_(''),'apache\logs',1); + puGeneral.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); +end; + +procedure TfMain.FormClose(Sender: TObject; var Action: TCloseAction); +begin + Action:=caHide; +end; + +procedure TfMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean); +begin + if WindowsShutdownInProgress then begin + CanClose:=true; + end else begin + CanClose:=false; + Hide; + end; +end; + +procedure TfMain.FormCreate(Sender: TObject); +var + isAdmin: Boolean; + xamppVersion: string; + CCVersion: string; +begin + TranslateComponent(Self); + + BaseDir:=LowerCase(ExtractFilePath(Application.ExeName)); + if GetComputerName='STRUEBER' then BaseDir:='c:\xampp'; + + AddLog(_('Initializing main')); + + left:=screen.WorkAreaWidth-Width; + top:=screen.WorkAreaHeight-Height; + + WinVersion:=GetWinVersion; + WindowsShutdownInProgress:=false; + AddLog(Format(_('Windows version: %s'),[WinVersion.WinVersion])); + xamppVersion:=TryGuessXamppVersion; + AddLog('Xampp version: '+xamppVersion); + + if cCompileDate<>'' then CCVersion:=GlobalProgramversion+Format(' [ Compiled: %s ]',[cCompileDate]) + else CCVersion:=GlobalProgramversion; + AddLog('Control center version: '+CCVersion); + + Caption:='XAMPP Control Panel v'+GlobalProgramversion+Format(' [ Compiled: %s ]',[cCompileDate]); + lHeader.Caption:='XAMPP Control Panel v'+GlobalProgramversion; + + CurrentUser:=GetCurrentUserName; + isAdmin:=IsWindowsAdmin; + if isAdmin then begin + AddLog(_('Running as admin - good!')); + end else begin + AddLog(_('Running not as admin! This will work for all application stuff, but whenever you do'),ltInfo); + AddLog(_('something with services there will be a security dialogue! So think about running'),ltInfo); + AddLog(_('this application with administrator rights!'),ltInfo); + end; + + AddLog(Format(_('Working with basedir: "%s"'),[BaseDir])); + + if BaseDir[length(BaseDir)]<>'\' then BaseDir:=BaseDir+'\'; + + NetStatTable.UpdateTable; + Processes.Update; + + AddLog(_('Initializing moduls')); + Apache:=tApache.Create( bApacheService, pApacheStatus, lApachePIDs, lApachePorts, bApacheAction, bApacheAdmin); + MySQL:=tMySQL.Create( bMySQLService, pMySQLStatus, lMySQLPIDs, lMySQLPorts, bMySQLAction, bMySQLAdmin); + FileZilla:=tFileZilla.Create( bFileZillaService, pFileZillaStatus, lFileZillaPIDs, lFileZillaPorts, bFileZillaAction, bFileZillaAdmin); + Mercury:=tMercury.Create( bMercuryService, pMercuryStatus, lMercuryPIDs, lMercuryPorts, bMercuryAction, bMercuryAdmin); + Tomcat:=tTomcat.Create( bTomcatService, pTomcatStatus, lTomcatPIDs, lTomcatPorts, bTomcatAction, bTomcatAdmin); + + if Config.ASApache then begin + Apache.AutoStart:=true; + AddLog(Format(_('Enabling autostart for module "%s"'),[Apache.ModuleName])); + end; + if Config.ASMySQL then begin + MySQL.AutoStart:=true; + AddLog(Format(_('Enabling autostart for module "%s"'),[MySQL.ModuleName])); + end; + if Config.ASFileZilla then begin + FileZilla.AutoStart:=true; + AddLog(Format(_('Enabling autostart for module "%s"'),[FileZilla.ModuleName])); + end; + if Config.ASMercury then begin + Mercury.AutoStart:=true; + AddLog(Format(_('Enabling autostart for module "%s"'),[Mercury.ModuleName])); + end; + if Config.ASTomcat then begin + Tomcat.AutoStart:=true; + AddLog(Format(_('Enabling autostart for module "%s"'),[Tomcat.ModuleName])); + end; + + + AddLog(_('Starting')+' check-timer'); + TimerUpdateStatus.Enabled:=true; +// UpdateStatusAll; +end; + + +procedure TfMain.FormDestroy(Sender: TObject); +begin + AddLog(_('Deinitializing moduls')); + Apache.Free; + MySQL.Free; + FileZilla.Free; + Mercury.Free; + AddLog(_('Deinitializing main')); + SaveLogFile; +end; + +procedure TfMain.TimerUpdateStatusTimer(Sender: TObject); +begin + UpdateStatusAll; +end; + +procedure TfMain.TrayIcon1DblClick(Sender: TObject); +begin + miShowHideClick(nil); +end; + + +function TfMain.TryGuessXamppVersion: string; +var ts: TStringList; + s: string; + p: Integer; +begin + result:='???'; + ts:=TStringList.Create; + try + ts.LoadFromFile(BaseDir+'\readme_de.txt'); + if ts.Count<1 then exit; + s:=LowerCase(ts[0]); + p:=pos('version',s); + if p=0 then exit; + delete(s,1,p+7); + p:=pos(' ',s); + if p=0 then exit; + result:=copy(s,1,p-1); + except + end; + ts.Free; +end; + +procedure DumpProcesses; +var ProcInfo: TProcInfo; + p: Integer; + s: string; +begin + for p:=0 to Processes.ProcessList.Count-1 do begin + ProcInfo:=Processes.ProcessList[p]; + s:=Format('%d %s',[ProcInfo.PID,ProcInfo.ExePath]); + fMain.reLog.Lines.Add(s) + end; +end; + +procedure TfMain.UpdateStatusAll; +begin + Processes.Update; + NetStatTable.UpdateTable; +// DumpProcesses; + + // 1. Check Apache + Apache.UpdateStatus; + + // 2. Check MySql + MySQL.UpdateStatus; + + // 3. Check Filezilla + FileZilla.UpdateStatus; + + // 4. Check Mercury + Mercury.UpdateStatus; + + // 5. Check Mercury + Tomcat.UpdateStatus; +end; + +procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession); +begin + WindowsShutdownInProgress:=true; + inherited; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMercury.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMercury.pas new file mode 100755 index 0000000000..6420ac0273 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMercury.pas @@ -0,0 +1,233 @@ +unit uMercury; + +interface + +uses GnuGettext, uBaseModule, SysUtils, Classes, Windows, ExtCtrls, StdCtrls, Buttons, + uNetstatTable, uTools, uProcesses; + +type + tMercury = class(tBaseModule) + OldPIDs, OldPorts: string; + procedure ServiceInstall; override; + procedure ServiceUnInstall; override; + procedure Start; override; + procedure Stop; override; + procedure Admin; override; + procedure UpdateStatus; override; + procedure AddLog(Log: string; LogType: tLogType=ltDefault); reintroduce; + constructor Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); + destructor Destroy; override; + end; + +implementation + +uses uMain; + +const +// cServiceName = 'Mercury'; + cModuleName = 'Mercury'; + + +var hWindow: HWND; + +{ tMercury } + +procedure tMercury.AddLog(Log: string; LogType: tLogType); +begin + inherited AddLog('mercury', Log, LogType); +end; + +function EnumProcess(hHwnd: HWND; lParam : integer): boolean; stdcall; +var + pPid : DWORD; + title, ClassName : string; +begin + if (hHwnd=0) then begin + result := false; + end else begin + GetWindowThreadProcessId(hHwnd,pPid); + SetLength(ClassName, 255); + SetLength(ClassName, GetClassName(hHwnd, PChar(className), Length(className))); + SetLength(title, 255); + SetLength(title, GetWindowText(hHwnd, PChar(title), Length(title))); +// fMain.Addlog( +// 'Class Name = ' + className + +// '; Title = ' + title + +// '; HWND = ' + IntToStr(hHwnd) + +// '; Pid = ' + IntToStr(pPid) +// ); + if title='Mercury/32' then begin + hWindow:=hHwnd; + end; + + Result := true; + end; +end; + +procedure tMercury.Admin; +begin + hWindow:=0; + EnumWindows(@EnumProcess,0); + if hWindow<>0 then + ShowWindow(hWindow,SW_SHOW); +end; + +constructor tMercury.Create; +const Ports:array[0..6] of integer=(25,79,105,106,110,143,2224); +var + PortBlocker: string; + ServerApp: string; + p: Integer; +// DidShowRunningWarn: Boolean; + BlockedPorts: string; +begin + inherited; + ModuleName:=cModuleName; + isService:=false; +// DidShowRunningWarn:=false; + AddLog(_('Initializing module...'),ltDebug); + ServerApp:=basedir+'MercuryMail\mercury.exe'; + if not FileExists(ServerApp) then + AddLog(Format(_('Possible problem detected: file "%s" not found - run this program from your XAMPP root directory!'),[ServerApp]),ltError); + + if Config.CheckDefaultPorts then begin + AddLog(_('Checking default ports...'),ltDebug); + for p:=Low(Ports) to High(Ports) do begin + + PortBlocker:=NetStatTable.isPortInUse(Ports[p]); + if (PortBlocker<>'') then begin + if (LowerCase(PortBlocker)=LowerCase(ServerApp)) then begin +// if NOT DidShowRunningWarn then +// AddLog(Format(_('"%s" seems to be running on port %d?'),[ServerApp,Ports[p]]),ltError); +// DidShowRunningWarn:=true; + if BlockedPorts='' then BlockedPorts:=InttoStr(Ports[p]) + else BlockedPorts:=BlockedPorts+', '+InttoStr(Ports[p]); + end else begin + AddLog('Possible problem detected: Port '+IntToStr(Ports[p])+' in use by "'+PortBlocker+'"!',ltError); + AddLog(Format(_('Possible problem detected: Port %d in use by "%s"!'),[Ports[p],PortBlocker]),ltError); + end; + end; + end; + if BlockedPorts<>'' then + AddLog(Format(_('"%s" seems to be running on port(s) %s?'),[ServerApp,BlockedPorts]),ltError); + end; +end; + +destructor tMercury.Destroy; +begin + + inherited; +end; + +procedure tMercury.ServiceInstall; +begin + inherited; + +end; + +procedure tMercury.ServiceUnInstall; +begin + inherited; + +end; + +procedure tMercury.Start; +var + App: string; +begin + App:=BaseDir+'MercuryMail\mercury.exe'; + AddLog(Format(_('Starting %s app...'),[cModuleName])); + Addlog(Format(_('Executing "%s"'),[App]),ltDebug); + RunProcess(App,SW_HIDE,false); + AddLog('Starting Mercury...'); +end; + +procedure tMercury.Stop; +var + App: string; +begin + AddLog('Stopping Mercury'); + Admin; + AddLog(_('Stopping')+' '+cModuleName); + App:=BaseDir+'apache\bin\pv.exe -f -c mercury.exe -q -e'; + Addlog(Format(_('Executing "%s"'),[App]),ltDebug); + RunProcess(App,SW_HIDE,false); +end; + +procedure tMercury.UpdateStatus; + var + p: Integer; + ProcInfo: TProcInfo; + s: string; + ports: string; +begin + if IsService then begin + end else begin + isRunning:=false; + PIDList.Clear; + for p:=0 to Processes.ProcessList.Count-1 do begin + ProcInfo:=Processes.ProcessList[p]; + if (pos(BaseDir,ProcInfo.ExePath)=1) and (pos('mercury.exe',ProcInfo.Module)=1) then begin + isRunning:=true; + PIDList.Add(Pointer(ProcInfo.PID)); + end; + end; + end; + s:=''; + // Checking processes + for p:=0 to PIDList.Count-1 do begin + if p=0 then s:=IntToStr(Integer(PIDList[p])) + else s:=s+#13+IntToStr(Integer(PIDList[p])); + end; + if s<>OldPIDs then begin + lPID.Caption:=s; + OldPIDs:=s; + end; + // Checking netstats + s:=''; + for p:=0 to PIDList.Count-1 do begin + ports:=NetStatTable.GetPorts4PID(Integer(PIDList[p])); + if ports<>'' then begin + if s='' then s:=ports + else s:=s+', '+ports; + end; + end; + if s<>OldPorts then begin + lPort.Caption:=s; + OldPorts:=s; + end; + + + if byte(isRunning)<>oldIsRunningByte then begin + + if oldIsRunningByte<>2 then begin + if isRunning then s:=_('running') + else s:=_('stopped'); + AddLog(_('Status change detected:')+' '+s); + end; + + oldIsRunningByte:=byte(isRunning); + if isRunning then begin + pStatus.Color:=cRunningColor; + bStartStop.Caption:=_('Stop'); + bAdmin.Enabled:=true; + end else begin + pStatus.Color:=cStoppedColor; + bStartStop.Caption:=_('Start'); + bAdmin.Enabled:=false; + end; + end; + + if AutoStart then begin + AutoStart:=false; + if isRunning then begin + AddLog(_('Autostart active: modul is already running - aborted'),ltError); + end else begin + AddLog(_('Autostart active: starting...')); + Start; + end; + end; + +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMySQL.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMySQL.pas new file mode 100755 index 0000000000..8cd0470e44 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uMySQL.pas @@ -0,0 +1,245 @@ +unit uMySQL; + +interface + +uses GnuGettext, uBaseModule, SysUtils, Classes, Windows, ExtCtrls, StdCtrls, Buttons, + uNetstatTable, uTools, uProcesses; + +type + tMySQL = class(tBaseModule) + OldPIDs, OldPorts: string; + procedure ServiceInstall; override; + procedure ServiceUnInstall; override; + procedure Start; override; + procedure Stop; override; + procedure Admin; override; + procedure UpdateStatus; override; + procedure CheckIsService; reintroduce; + procedure AddLog(Log: string; LogType: tLogType=ltDefault); reintroduce; + constructor Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); + destructor Destroy; override; + end; + +implementation + +const cServiceName = 'mysql'; + cModuleName = 'mysql'; + +{ tMySQL } + +procedure tMySQL.AddLog(Log: string; LogType: tLogType); +begin + inherited AddLog('mysql', Log, LogType); +end; + +procedure tMySQL.Admin; +var + App, Param: string; +begin + Param:='http://localhost/phpmyadmin/'; + if Config.BrowserApp<>'' then begin + App:=Config.BrowserApp; + ExecuteFile(App,Param,'',SW_SHOW); + Addlog(Format(_('Executing "%s" "%s"'),[App,Param]),ltDebug); + end else begin + ExecuteFile(Param,'','',SW_SHOW); + Addlog(Format(_('Executing "%s"'),[Param]),ltDebug); + end; +end; + +procedure tMySQL.CheckIsService; +var + s: string; +begin + inherited CheckIsService(cServiceName); + if isService then s:=_('Service installed') + else s:=_('Service not installed'); + AddLog(Format(_('Checking for service (name="%s"): %s'),[cServiceName,s]),ltDebug); +end; + +constructor tMySQL.Create; +var + PortBlocker: string; + ServerApp: string; + ServerPort: Integer; +begin + inherited; + ModuleName:=cModuleName; + AddLog(_('Initializing module...'),ltDebug); + ServerApp:=basedir+'mysql\bin\mysqld.exe'; + if not FileExists(ServerApp) then + AddLog(Format(_('Possible problem detected: file "%s" not found - run this program from your XAMPP root directory!'),[ServerApp]),ltError); + + CheckIsService; + + if Config.CheckDefaultPorts then begin + ServerPort:=3306; + AddLog(_('Checking default ports...'),ltDebug); + PortBlocker:=NetStatTable.isPortInUse(ServerPort); + if (PortBlocker<>'') then begin + if (LowerCase(PortBlocker)=LowerCase(ServerApp)) then begin + AddLog(Format(_('"%s" seems to be running on port %d?'),[ServerApp,ServerPort]),ltError); + end else begin + AddLog(Format(_('Possible problem detected: Port %d in use by "%s"!'),[ServerPort,PortBlocker]),ltError); + end; + end; + end; +end; + +destructor tMySQL.Destroy; +begin + + inherited; +end; + +procedure tMySQL.ServiceInstall; +var + App, Param: string; + RC: Integer; +begin + App:=BaseDir+'\mysql\bin\mysqld.exe'; + Param:='--install mysql --defaults-file='+BaseDir+'\mysql\bin\my.ini'; + AddLog(_('Installing service...')); + Addlog(Format(_('Executing %s %s'),[App,Param]),ltDebug); + RC:=RunAsAdmin(App,Param,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); +end; + +procedure tMySQL.ServiceUnInstall; +var + App, Param: string; + RC: Cardinal; +begin + App:=BaseDir+'\mysql\bin\mysqld.exe'; + Param:='--remove mysql'; + AddLog('Uninstalling service...'); + Addlog(Format(_('Executing %s %s'),[App,Param]),ltDebug); + RC:=RunAsAdmin(App,Param,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); +end; + + +procedure tMySQL.Start; +var + App: string; + RC: Cardinal; +begin + if isService then begin + AddLog(Format(_('Starting %s service...'),[cModuleName])); + App:=Format('start "%s"',[cServiceName]); + Addlog(Format(_('Executing "%s"'),['net '+App]),ltDebug); + RC:=RunAsAdmin('net',App,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); + end else begin + AddLog(Format(_('Starting %s app...'),[cModuleName])); + App:=BaseDir+'mysql\bin\mysqld.exe --defaults-file='+BaseDir+'mysql\bin\my.ini --standalone'; + Addlog(Format(_('Executing "%s"'),[App]),ltDebug); + RunProcess(App,SW_HIDE,false); + end; +end; + +procedure tMySQL.Stop; +var + i, pPID: Integer; + App: string; + RC: Cardinal; +begin + if isService then begin + AddLog(Format(_('Stopping %s service...'),[cModuleName])); + App:=Format('stop "%s"',[cServiceName]); + Addlog(Format(_('Executing "%s"'),['net '+App]),ltDebug); + RC:=RunAsAdmin('net',App,SW_HIDE); + if RC=0 then AddLog(Format(_('Return code: %d'),[RC]),ltDebug) + else AddLog(Format(_('There may be an error, return code: %d - %s'),[RC,SystemErrorMessage(RC)]),ltError); + end else begin + if PIDList.Count>0 then begin + for i:=0 to PIDList.Count-1 do begin + pPID:=Integer(PIDList[i]); + AddLog(_('Stopping')+' '+cModuleName+' '+Format('(PID: %d)',[pPID])); + App:=Format(BaseDir+'apache\bin\pv.exe -f -k -q -i %d',[pPID]); + Addlog(Format(_('Executing "%s"'),[App]),ltDebug); + RunProcess(App,SW_HIDE,false); + end; + end else begin + AddLog(_('No PIDs found?!')); + end; + end; +end; + +procedure tMySQL.UpdateStatus; +var + p: Integer; + ProcInfo: TProcInfo; + s: string; + ports: string; +begin + isRunning:=false; + PIDList.Clear; + for p:=0 to Processes.ProcessList.Count-1 do begin + ProcInfo:=Processes.ProcessList[p]; + if { (pos(BaseDir,ProcInfo.ExePath)=1) and } (pos('mysqld.exe',ProcInfo.Module)=1) then begin + isRunning:=true; + PIDList.Add(Pointer(ProcInfo.PID)); + end; + end; + + // Checking processes + s:=''; + for p:=0 to PIDList.Count-1 do begin + if p=0 then s:=IntToStr(Integer(PIDList[p])) + else s:=s+#13+IntToStr(Integer(PIDList[p])); + end; + if s<>OldPIDs then begin + lPID.Caption:=s; + OldPIDs:=s; + end; + + // Checking netstats + s:=''; + for p:=0 to PIDList.Count-1 do begin + ports:=NetStatTable.GetPorts4PID(Integer(PIDList[p])); + if ports<>'' then begin + if s='' then s:=ports + else s:=s+', '+ports; + end; + end; + if s<>OldPorts then begin + lPort.Caption:=s; + OldPorts:=s; + end; + + if byte(isRunning)<>oldIsRunningByte then begin + + if oldIsRunningByte<>2 then begin + if isRunning then s:=_('running') + else s:=_('stopped'); + AddLog(_('Status change detected:')+' '+s); + end; + + oldIsRunningByte:=byte(isRunning); + if isRunning then begin + pStatus.Color:=cRunningColor; + bStartStop.Caption:=_('Stop'); + bAdmin.Enabled:=true; + end else begin + pStatus.Color:=cStoppedColor; + bStartStop.Caption:=_('Start'); + bAdmin.Enabled:=false; + end; + end; + + if AutoStart then begin + AutoStart:=false; + if isRunning then begin + AddLog(_('Autostart active: modul is already running - aborted'),ltError); + end else begin + AddLog(_('Autostart active: starting...')); + Start; + end; + end; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstat.dfm b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstat.dfm new file mode 100755 index 0000000000..2390ab6e47 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstat.dfm @@ -0,0 +1,134 @@ +object fNetstat: TfNetstat + Left = 394 + Top = 196 + BorderStyle = bsSizeToolWin + Caption = 'Netstat - TCP Listening sockets' + ClientHeight = 580 + ClientWidth = 632 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + Position = poScreenCenter + OnClose = FormClose + OnCreate = FormCreate + OnDestroy = FormDestroy + OnShow = FormShow + DesignSize = ( + 632 + 580) + PixelsPerInch = 96 + TextHeight = 13 + object ListView1: TListView + Left = 4 + Top = 28 + Width = 624 + Height = 529 + Anchors = [akLeft, akTop, akRight, akBottom] + Columns = < + item + Caption = 'Address' + Width = 100 + end + item + Alignment = taRightJustify + Caption = 'Port' + end + item + Alignment = taRightJustify + Caption = 'PID' + end + item + Caption = 'Name' + Width = 400 + end> + DoubleBuffered = True + OwnerData = True + ReadOnly = True + RowSelect = True + ParentDoubleBuffered = False + TabOrder = 0 + ViewStyle = vsReport + OnColumnClick = ListView1ColumnClick + OnCustomDrawItem = ListView1CustomDrawItem + OnData = ListView1Data + end + object BitBtn1: TBitBtn + Left = 549 + Top = 4 + Width = 75 + Height = 21 + Anchors = [akTop, akRight] + Caption = 'Refresh' + DoubleBuffered = True + ModalResult = 4 + NumGlyphs = 2 + ParentDoubleBuffered = False + TabOrder = 1 + OnClick = BitBtn1Click + end + object StatusBar1: TStatusBar + Left = 0 + Top = 561 + Width = 632 + Height = 19 + Panels = <> + end + object Panel1: TPanel + Left = 4 + Top = 4 + Width = 80 + Height = 18 + Caption = 'Active socket' + Color = clWindow + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentBackground = False + ParentFont = False + TabOrder = 3 + end + object Panel2: TPanel + Left = 176 + Top = 4 + Width = 80 + Height = 18 + Caption = 'Old socket' + Color = clMaroon + Font.Charset = DEFAULT_CHARSET + Font.Color = clWhite + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentBackground = False + ParentFont = False + TabOrder = 4 + end + object Panel3: TPanel + Left = 90 + Top = 4 + Width = 80 + Height = 18 + Caption = 'New socket' + Color = clLime + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentBackground = False + ParentFont = False + TabOrder = 5 + end + object TimerUpdate: TTimer + Interval = 500 + OnTimer = TimerUpdateTimer + Left = 20 + Top = 84 + end +end diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstat.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstat.pas new file mode 100755 index 0000000000..50bba4fa18 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstat.pas @@ -0,0 +1,255 @@ +unit uNetstat; + +interface + +uses + GnuGettext, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, ComCtrls, StdCtrls, Buttons, uNetstatTable, uTools, ExtCtrls, uProcesses; + +type + tNetState = (nsActive, nsOld, nsNew, nsUpdatingActive, nsUpdatingNew); + tNetEntry = class + AddrStr: string; + AddrR: Cardinal; + Port: integer; + PID: integer; + ProcName: string; + State:tNetState; + end; + + TfNetstat = class(TForm) + ListView1: TListView; + BitBtn1: TBitBtn; + StatusBar1: TStatusBar; + TimerUpdate: TTimer; + Panel1: TPanel; + Panel2: TPanel; + Panel3: TPanel; + procedure BitBtn1Click(Sender: TObject); + procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn); + procedure FormCreate(Sender: TObject); + procedure TimerUpdateTimer(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure ListView1Data(Sender: TObject; Item: TListItem); + procedure ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem; + State: TCustomDrawState; var DefaultDraw: Boolean); + procedure cbShowCSRSSClick(Sender: TObject); + procedure FormDestroy(Sender: TObject); + private + NetEntryList: tList; + procedure ClearnetEntryList; + function FindNetEntry(AddrR: Cardinal; Port,PID: integer; ProcName: string): tNetEntry; + public + procedure RefreshTable(ResetStates: boolean); + end; + +var + fNetstat: TfNetstat; + +implementation + +uses uMain; + +const cModuleName = 'netstat'; + +var LastSortID: integer; + +{$R *.dfm} + +procedure TfNetstat.BitBtn1Click(Sender: TObject); +begin + RefreshTable(true); +end; + + +procedure TfNetstat.cbShowCSRSSClick(Sender: TObject); +begin + RefreshTable(true); +end; + +procedure TfNetstat.ClearnetEntryList; +var i:integer; + NE: tNetEntry; +begin + ListView1.Items.Count:=0; + for i:=0 to NetEntryList.Count-1 do begin + NE:=NetEntryList[i]; + NE.Free; + end; + NetEntryList.Clear; +end; + +function TfNetstat.FindNetEntry(AddrR: Cardinal; Port, PID: integer; ProcName: string): tNetEntry; +var i:integer; + NE: tNetEntry; +begin + for i:=0 to NetEntryList.Count-1 do begin + NE:=NetEntryList[i]; + if (NE.AddrR=AddrR) and (NE.Port=Port) and (NE.PID=PID) and (NE.ProcName=ProcName) then begin + result:=NE; + exit; + end; + end; + result:=nil; +end; + +procedure TfNetstat.FormClose(Sender: TObject; var Action: TCloseAction); +begin + ClearnetEntryList; +end; + +procedure TfNetstat.FormCreate(Sender: TObject); +begin + TranslateComponent(Self); + NetEntryList:=TList.Create; + LastSortID:=0; +end; + +procedure TfNetstat.FormDestroy(Sender: TObject); +begin + NetEntryList.Free; +end; + +procedure TfNetstat.FormShow(Sender: TObject); +begin + TimerUpdate.Enabled:=true; +end; + +function smallnumber(i: Int64):integer; +begin + if i>0 then result:=1 + else if i<0 then result:=-1 + else result:=0; +end; + +function CustomSortProc(Item1, Item2: Pointer): integer; // stdcall; +var NE1, NE2: tNetEntry; +begin + NE1:=Item1; + NE2:=Item2; + + case LastSortID of + 0: Result := smallnumber(Int64(NE1.AddrR)-Int64(NE2.AddrR))*4+smallnumber(NE1.Port-NE2.Port)*2+smallnumber(NE1.PID-NE2.PID)*1; + 1: Result := smallnumber(Int64(NE1.AddrR)-Int64(NE2.AddrR))*2+smallnumber(NE1.Port-NE2.Port)*4+smallnumber(NE1.PID-NE2.PID)*1; + 2: Result := smallnumber(Int64(NE1.AddrR)-Int64(NE2.AddrR))*2+smallnumber(NE1.Port-NE2.Port)*1+smallnumber(NE1.PID-NE2.PID)*4; + 3: Result := smallnumber(CompareText(NE1.ProcName, NE2.ProcName))*8+smallnumber(Int64(NE1.AddrR)-Int64(NE2.AddrR))*4+smallnumber(NE1.Port-NE2.Port)*2+smallnumber(NE1.PID-NE2.PID)*1; + else Result:=0; + end; +end; + +procedure TfNetstat.ListView1ColumnClick(Sender: TObject; Column: TListColumn); +begin + LastSortID:=Column.Index; + NetEntryList.Sort(CustomSortProc); + ListView1.Refresh; +end; + +procedure TfNetstat.ListView1CustomDrawItem(Sender: TCustomListView; + Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean); +var NE: tNetEntry; +begin + NE:=NetEntryList[Item.Index]; + + case NE.State of + nsActive: begin ListView1.Canvas.Font.Color := clWindowText; ListView1.Canvas.Brush.Color:=clWindow; end; +// nsOld: begin ListView1.Canvas.Font.Color := clGrayText; ListView1.Canvas.Brush.Color:=clWindow; end; + nsOld: begin ListView1.Canvas.Font.Color := clWhite; ListView1.Canvas.Brush.Color:=clMaroon; end; + nsNew: begin ListView1.Canvas.Font.Color := clWindowText; ListView1.Canvas.Brush.Color:=clLime; end; + nsUpdatingActive, nsUpdatingNew: begin ListView1.Canvas.Font.Color := clGrayText; ListView1.Canvas.Brush.Color:=clBlue; end; + end; + +end; + +procedure TfNetstat.ListView1Data(Sender: TObject; Item: TListItem); +var NE: tNetEntry; +begin + NE:=NetEntryList[Item.Index]; + Item.Caption:=NE.AddrStr; + Item.SubItems.Add(IntToStr(NE.Port)); + Item.SubItems.Add(IntToStr(NE.PID)); + Item.SubItems.Add(NE.ProcName); +end; + +procedure TfNetstat.RefreshTable(ResetStates: boolean); +var + i: integer; + NE: tNetEntry; + ProcInfo: tProcInfo; + PID, Addr, AddrR, Port: Cardinal; + PIDName: string; + AddrStr: string; +begin + NetStatTable.UpdateTable; + + ListView1.Items.BeginUpdate; + + if ResetStates then ClearnetEntryList; + + for i:=0 to NetEntryList.Count-1 do begin + NE:=NetEntryList[i]; + if NE.State=nsActive then NE.State:=nsUpdatingActive; + if NE.State=nsNew then NE.State:=nsUpdatingNew; + end; + + for i:=0 to NetStatTable.pTcpTable.dwNumEntries-1 do begin + if NetStatTable.pTcpTable.table[i].dwOwningPid<>0 then begin + PID:=NetStatTable.pTcpTable.table[i].dwOwningPid; + Addr:=NetStatTable.pTcpTable.table[i].dwLocalAddr; + AddrR:= + ((Addr and $FF000000) shr 24) or + ((Addr and $00FF0000) shr 08) or + ((Addr and $0000FF00) shl 08) or + ((Addr and $000000FF) shl 24); + + AddrStr:=Cardinal2IP(Addr); + Port:=NetStatTable.pTcpTable.table[i].dwLocalPort; + ProcInfo:=Processes.GetProcInfo(PID); + if ProcInfo<>nil then begin + PIDName:=ProcInfo.ExePath; + NE:=FindNetEntry(AddrR, Port, PID, PIDName); + if NE=nil then begin + NE:=tNetEntry.Create; + NE.AddrStr:=AddrStr; + NE.AddrR:=AddrR; + NE.Port:=Port; + NE.PID:=PID; + NE.ProcName:=PIDName; + NE.State:=nsNew; + NetEntryList.Add(NE); + ListView1.Items.Count:=ListView1.Items.Count+1; + + fMain.AddLog(cModuleName,Format(_('New listening socket: %s:%d'),[NE.AddrStr,NE.Port]),ltDebug); + + end else begin + if NE.State=nsUpdatingActive then NE.State:=nsActive; + if NE.State=nsUpdatingNew then NE.State:=nsNew; + end; + end; + end; + end; + + for i:=0 to NetEntryList.Count-1 do begin + NE:=NetEntryList[i]; + if ResetStates then NE.State:=nsActive; + if (NE.State=nsUpdatingActive) or (NE.State=nsUpdatingNew) then begin + NE.State:=nsOld; + fMain.AddLog(cModuleName,Format(_('Listening socket closed: %s:%d'),[NE.AddrStr,NE.Port]),ltDebug); + end; + end; + + NetEntryList.Sort(CustomSortProc); + ListView1.Items.EndUpdate; + ListView1.Refresh; +end; + +procedure TfNetstat.TimerUpdateTimer(Sender: TObject); +begin + if Visible then begin + RefreshTable(false); + end else begin + TimerUpdate.Enabled:=false; + end; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstatTable.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstatTable.pas new file mode 100755 index 0000000000..4876b46b4c --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uNetstatTable.pas @@ -0,0 +1,192 @@ +unit uNetstatTable; + +interface + +uses GnuGettext, SysUtils, Classes, Windows, Dialogs, uProcesses; + +const + MIB_TCP_STATE_LISTEN = 2; + +type + MIB_TCPROW_OWNER_PID = packed record + dwState: DWORD; + dwLocalAddr: DWORD; + dwLocalPort: DWORD; + dwRemoteAddr: DWORD; + dwRemotePort: DWORD; + dwOwningPid: DWORD; + end; +// PMIB_TCPROW_OWNER_PID = ^MIB_TCPROW_OWNER_PID; + + MIB_TCPTABLE_OWNER_PID = packed record + dwNumEntries: DWord; + table: array [0..99999] of MIB_TCPROW_OWNER_PID ; + end; + PMIB_TCPTABLE_OWNER_PID = ^MIB_TCPTABLE_OWNER_PID; + + + tNetstatTable = class + private + hLibModule: THandle; + DLLProcPointer: Pointer; + procedure LoadExIpHelperProcedures; + procedure UnLoadExIpHelperProcedures; + public + pTcpTable: PMIB_TCPTABLE_OWNER_PID; + procedure UpdateTable; + function GetPorts4PID(pid: integer):string; + function GetPortCount4PID(pid: integer):integer; + function isPortInUse(port: integer): string; + constructor Create; + destructor Destroy; override; + end; + + +var + NetStatTable: tNetstatTable; + + +implementation + +uses uMain, uTools; + +const + TCP_TABLE_OWNER_PID_ALL = 5; + AF_INET = 2; + +type + TCP_TABLE_CLASS = Integer; + ULONG = Integer; + TGetExtendedTcpTable = function(pTcpTable: Pointer; dwSize: PDWORD; bOrder: BOOL; lAf: ULONG; TableClass: TCP_TABLE_CLASS; Reserved: ULONG): DWord;stdcall; + + +{ tNetStatTable } + +constructor tNetstatTable.Create; +begin + DLLProcPointer:=nil; + hLibModule:=0; + pTCPTable:=nil; + try + LoadExIpHelperProcedures; + except + end; +end; + +destructor tNetstatTable.Destroy; +begin + try + UnLoadExIpHelperProcedures; + except + end; + inherited; +end; + +function tNetstatTable.GetPortCount4PID(pid: integer): integer; +var i: integer; + port: string; +begin + result:=0; + for i:=0 to NetStatTable.pTcpTable.dwNumEntries-1 do + if NetStatTable.pTcpTable.table[i].dwOwningPid=Cardinal(pid) then + result:=result+1; +end; + +function tNetstatTable.GetPorts4PID(pid: integer): string; +var i: integer; + port: string; +begin + result:=''; + for i:=0 to NetStatTable.pTcpTable.dwNumEntries-1 do begin + if NetStatTable.pTcpTable.table[i].dwOwningPid=Cardinal(pid) then begin + port:=IntToStr(NetStatTable.pTcpTable.table[i].dwLocalPort); + if result='' then result:=port + else result:=result+', '+port; + end; + end; +end; + +function tNetstatTable.isPortInUse(port: integer): string; +var i: integer; + ProcInfo: TProcInfo; + pid: Cardinal; +begin + result:=''; + for i:=0 to NetStatTable.pTcpTable.dwNumEntries-1 do begin + if NetStatTable.pTcpTable.table[i].dwLocalPort=Cardinal(port) then begin + pid:=NetStatTable.pTcpTable.table[i].dwOwningPid; + ProcInfo:=Processes.GetProcInfo(pid); + if ProcInfo<>nil then result:=ProcInfo.ExePath + else result:=_('unknown program'); + exit; + end; + end; +end; + +procedure tNetstatTable.LoadExIpHelperProcedures; +begin + hLibModule:=LoadLibrary('iphlpapi.dll'); + if hLibModule=0 then exit; + DLLProcPointer:=GetProcAddress(hLibModule,'GetExtendedTcpTable'); + if not Assigned(DLLProcPointer) then begin + ShowMessage(IntToStr(GetLastError)); + end; +end; + +procedure tNetstatTable.UnLoadExIpHelperProcedures; +begin + if hLibModule>HINSTANCE_ERROR then + FreeLibrary(hLibModule); +end; + +procedure tNetstatTable.UpdateTable; +var + dwSize: DWord; + Res: Dword; + GetExtendedTcpTable: TGetExtendedTcpTable; + i: integer; +begin + if pTcpTable<>nil then begin + FreeMem(pTcpTable); + pTCPTable:=nil; + end; + + if (DLLProcPointer=nil) or (hLibModule Nil) Then FreeMem(pTcpTable); + end; +end; + +initialization + +NetStatTable:=tNetstatTable.Create; + +finalization + +NetStatTable.Free; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uProcesses.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uProcesses.pas new file mode 100755 index 0000000000..fb5cae01b7 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uProcesses.pas @@ -0,0 +1,147 @@ +unit uProcesses; + +interface + +uses GnuGettext, TlHelp32, uTools, Classes, SysUtils, Windows; + +type + TProcInfo = class + PID: integer; + Module, ExePath: String; + CanDelete: boolean; + end; + + tProcesses = class + public + ProcessList: tList; + function GetProcInfo(PID: integer):TProcInfo; + procedure Update; + constructor Create; + destructor Destroy; override; + end; + + +var + Processes: tProcesses; + +implementation + +uses uMain; + +const cModuleName = 'procs'; + +{ tProcessList } + +constructor tProcesses.Create; +begin + ProcessList:=TList.Create; +end; + +destructor tProcesses.Destroy; +var + ProcInfo: TProcInfo; + p: Integer; +begin + for p:=0 to ProcessList.Count-1 do begin + ProcInfo:=ProcessList[p]; + FreeAndNil(ProcInfo); + end; + FreeAndNil(ProcessList); + inherited; +end; + +function tProcesses.GetProcInfo(PID: integer): TProcInfo; +var + ProcInfo: TProcInfo; + p: Integer; +begin + for p:=0 to ProcessList.Count-1 do begin + ProcInfo:=ProcessList[p]; + if ProcInfo.PID=pid then begin + result:=ProcInfo; + exit; + end; + end; + Result:=nil; +end; + +procedure tProcesses.Update; +var hProcessSnap: tHandle; + TProcessEntry: TProcessEntry32; + ProcInfo: TProcInfo; + hModuleSnap: tHandle; + ModuleEntry: MODULEENTRY32; + i: Integer; + PID: Cardinal; +begin +// fMain.AddLog('processes', 'Checking processes...', ltDebugDetails); + + for i:=0 to ProcessList.Count-1 do begin + ProcInfo:=ProcessList[i]; + ProcInfo.CanDelete:=true; +// FreeAndNil(ProcInfo); + end; +// ProcessList.Clear; + + hProcessSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap = INVALID_HANDLE_VALUE) then exit; + TProcessEntry.dwSize := SizeOf(TProcessEntry); + if (Process32First(hProcessSnap, TProcessEntry)) then begin + repeat + PID:=TProcessEntry.th32ProcessID; + ProcInfo:=GetProcInfo(PID); + if ProcInfo<>nil then begin + ProcInfo.CanDelete:=false + end else begin +// hModuleSnap := INVALID_HANDLE_VALUE; + hModuleSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, TProcessEntry.th32ProcessID ); + if (hModuleSnap <> INVALID_HANDLE_VALUE) then begin + ModuleEntry.dwSize := sizeof(MODULEENTRY32); + if (Module32First(hModuleSnap, &ModuleEntry)) then begin + ProcInfo:=TProcInfo.Create; + ProcInfo.Module:=LowerCase(ModuleEntry.szModule); + ProcInfo.ExePath:=LowerCase(ModuleEntry.szExePath); + ProcInfo.PID:=TProcessEntry.th32ProcessID; + ProcInfo.CanDelete:=false; + ProcessList.Add(ProcInfo); + end else begin + ProcInfo:=nil; + end; + end else begin + ProcInfo:=TProcInfo.Create; + ProcInfo.Module:=LowerCase(TProcessEntry.szExeFile); + ProcInfo.ExePath:=LowerCase(TProcessEntry.szExeFile); + ProcInfo.PID:=TProcessEntry.th32ProcessID; + ProcInfo.CanDelete:=false; + ProcessList.Add(ProcInfo); + end; + if ProcInfo<>nil then + fMain.AddLog(cModuleName,Format(_('Creating PID-entry %d: %s'),[ProcInfo.PID,ProcInfo.ExePath]),ltDebugDetails); + CloseHandle(hModuleSnap); + end; + until not (Process32Next(hProcessSnap, TProcessEntry)); + end; + CloseHandle(hProcessSnap); + + i:=0; + while i'' then begin + App:=Config.BrowserApp; + ExecuteFile(App,Param,'',SW_SHOW); + Addlog(Format(_('Executing "%s" "%s"'),[App,Param]),ltDebug); + end else begin + ExecuteFile(Param,'','',SW_SHOW); + Addlog(Format(_('Executing "%s"'),[Param]),ltDebug); + end; +end; + +procedure tTomcat.CheckIsService; +begin + isService:=false; +end; + +constructor tTomcat.Create(pbbService: TBitBtn; pStatusPanel: tPanel; pPIDLabel, + pPortLabel: tLabel; pStartStopButton, pAdminButton: tBitBtn); +const Ports:array[0..2] of integer=(1,2,3); +var + PortBlocker: string; + ServerApp1, ServerApp2: string; + p: integer; +begin + inherited; + ModuleName:=cModuleName; + AddLog(_('Initializing module...'),ltDebug); + ServerApp1:=basedir+'tomcat\bin\tomcat6.exe'; + ServerApp2:=basedir+'tomcat\bin\tomcat7.exe'; + if ((not FileExists(ServerApp1)) and (not FileExists(ServerApp2))) then + AddLog(Format(_('Possible problem detected: file "%s" not found - run this program from your XAMPP root directory!'),[ServerApp1+'/'+ServerApp2]),ltError); + + + + CheckIsService; + + if Config.CheckDefaultPorts then begin + AddLog(_('Checking default ports...'),ltDebug); + + for p:=Low(Ports) to High(Ports) do begin + PortBlocker:=NetStatTable.isPortInUse(Ports[p]); + if (PortBlocker<>'') then begin + if (LowerCase(PortBlocker)=LowerCase(ServerApp1)) then begin + AddLog(Format(_('"%s" seems to be running on port %d?'),[ServerApp1,Ports[p]]),ltError); + end else if (LowerCase(PortBlocker)=LowerCase(ServerApp2)) then begin + AddLog(Format(_('"%s" seems to be running on port %d?'),[ServerApp2,Ports[p]]),ltError); + end else begin + AddLog(Format(_('Possible problem detected: Port %d in use by "%s"!'),[Ports[p],PortBlocker]),ltError); + end; + end; + end; + + end; + +end; + +destructor tTomcat.Destroy; +begin + + inherited; +end; + +procedure tTomcat.ServiceInstall; +begin + inherited; + +end; + +procedure tTomcat.ServiceUnInstall; +begin + inherited; + +end; + +procedure tTomcat.Start; +var + App, Param: string; +begin + AddLog(Format(_('Starting %s app...'),[cModuleName])); + Param:='/c '+BaseDir+'catalina_start.bat'; + if FileExists('c:\Windows\sysnative\cmd.exe') then app:='c:\Windows\sysnative\cmd.exe' // 64 bit? dann DIESE shell starten! + else app:='cmd'; + Addlog(Format(_('Executing "%s" "%s"'),[App,Param]),ltDebug); + if Config.TomcatVisible then + ExecuteFile(App,Param,BaseDir,SW_MINIMIZE) + else + ExecuteFile(App,Param,BaseDir,SW_HIDE) +end; + +procedure tTomcat.Stop; +var + App, Param: string; +begin + AddLog(_('Stopping')); + Param:='/c '+BaseDir+'catalina_stop.bat'; + if FileExists('c:\Windows\sysnative\cmd.exe') then app:='c:\Windows\sysnative\cmd.exe' // 64 bit? dann DIESE shell starten! + else app:='cmd'; + Addlog(Format(_('Executing "%s" "%s"'),[App,Param]),ltDebug); + ExecuteFile(App,Param,BaseDir,SW_HIDE); +end; + +procedure tTomcat.UpdateStatus; +var + p: Integer; + ProcInfo: TProcInfo; + s: string; + ports: string; +begin + isRunning:=false; + PIDList.Clear; + for p:=0 to Processes.ProcessList.Count-1 do begin + ProcInfo:=Processes.ProcessList[p]; + if { (pos(BaseDir,ProcInfo.ExePath)=1) and } (pos('java.exe',ProcInfo.Module)=1) then begin + + // Sind genau 3 Ports offen? + if NetStatTable.GetPortCount4PID(ProcInfo.PID)=3 then begin + isRunning:=true; + PIDList.Add(Pointer(ProcInfo.PID)); + end; + end; + end; + + // Checking processes + s:=''; + for p:=0 to PIDList.Count-1 do begin + if p=0 then s:=IntToStr(Integer(PIDList[p])) + else s:=s+#13+IntToStr(Integer(PIDList[p])); + end; + if s<>OldPIDs then begin + lPID.Caption:=s; + OldPIDs:=s; + end; + + // Checking netstats + s:=''; + for p:=0 to PIDList.Count-1 do begin + ports:=NetStatTable.GetPorts4PID(Integer(PIDList[p])); + if ports<>'' then begin + if s='' then s:=ports + else s:=s+', '+ports; + end; + end; + if s<>OldPorts then begin + lPort.Caption:=s; + OldPorts:=s; + end; + + if byte(isRunning)<>oldIsRunningByte then begin + + if oldIsRunningByte<>2 then begin + if isRunning then s:=_('running') + else s:=_('stopped'); + AddLog(_('Status change detected:')+' '+s); + end; + + oldIsRunningByte:=byte(isRunning); + if isRunning then begin + pStatus.Color:=cRunningColor; + bStartStop.Caption:=_('Stop'); + bAdmin.Enabled:=true; + end else begin + pStatus.Color:=cStoppedColor; + bStartStop.Caption:=_('Start'); + bAdmin.Enabled:=false; + end; + end; + + if AutoStart then begin + AutoStart:=false; + if isRunning then begin + AddLog(_('Autostart active: modul is already running - aborted'),ltError); + end else begin + AddLog(_('Autostart active: starting...')); + Start; + end; + end; +end; + +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uTools.pas b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uTools.pas new file mode 100755 index 0000000000..3a3add52a6 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/uTools.pas @@ -0,0 +1,437 @@ +unit uTools; + +interface + +uses GnuGettext, Classes, Graphics, Windows, SysUtils, TlHelp32, ShellAPI, Forms, Dialogs, IniFiles, VersInfo; + +const +// cIdxApache = 1; +// cIdxMySQL = 2; +// cIdxFileZilla = 3; +// cIdxMercury = 4; + cRunningColor = 200 * $10000 + 255*$100 + 200; + cStoppedColor = clBtnFace; +// SW_HIDE = Windows.SW_HIDE; + + cCompileDate='May 14th 2011 - build #1'; + cr=#13#10; + + +type + tLogType = (ltDefault, ltInfo, ltError, ltDebug, ltDebugDetails); + + TWinVersion = record + WinPlatForm: Byte; // VER_PLATFORM_WIN32_NT, VER_PLATFORM_WIN32_WINDOWS + WinVersion: string; + Major: Cardinal; + Minor: Cardinal; + end; + + tConfig = class + EditorApp: string; + BrowserApp: string; + ShowDebug: boolean; + DebugLevel: integer; + CheckDefaultPorts: boolean; + Language: string; + TomcatVisible: boolean; + + ASApache: boolean; + ASMySQL: boolean; + ASFileZilla: boolean; + ASMercury: boolean; + ASTomcat: boolean; + + UserLogs: record + Apache: tStringList; + MySQL: tStringList; + FileZilla: tStringList; + Mercury: tStringList; + Tomcat: tStringList; + end; + UserConfig: record + Apache: tStringList; + MySQL: tStringList; + FileZilla: tStringList; + Mercury: tStringList; + Tomcat: tStringList; + end; + + constructor Create; + destructor Destroy; override; + end; + +var + WinVersion: TWinVersion; + CurrentUser: string; + BaseDir: string; + CachedComputerName: string; + Config: tConfig; + IniFileName: string; + dfsVersionInfoResource1: TdfsVersionInfoResource; + GlobalProgramversion: string; + +procedure LoadSettings; +procedure SaveSettings; +function IsWindowsAdmin: Boolean; +function IntToServiceApp(b: boolean):string; +function GetCurrentUserName: string; +function GetWinVersion: TWinVersion; +function RunProcess(FileName: string; ShowCmd: DWORD; wait: Boolean; ProcID: PCardinal=nil): Longword; +function ExecuteFile(FileName, Params, DefaultDir: string; ShowCmd: Integer): THandle; +function RunAsAdmin(filename, parameters: string; ShowCmd: Integer): Cardinal; +function Cardinal2IP(C: Cardinal):string; +function GetComputerName:string; +function SystemErrorMessage(WinErrorCode: Cardinal): string; +function GetSystemLangShort:string; + + +implementation + +procedure LoadSettings; +var mi: TMemIniFile; +begin +// fMain.AddLog('Loading settings from configurationfile: "'+IniFileName+'"',ltDebug); + + mi:=nil; + try + mi:=TMemIniFile.Create(IniFileName); + Config.EditorApp:=mi.ReadString('Common','Editor','notepad.exe'); + Config.BrowserApp:=mi.ReadString('Common','Browser',''); + Config.ShowDebug:=mi.ReadBool('Common','Debug',false); + Config.DebugLevel:=mi.ReadInteger('Common','Debuglevel',0); + Config.CheckDefaultPorts:=mi.ReadBool('Common','CheckDefaultPorts',true); + Config.Language:=mi.ReadString('Common','Language',''); + Config.TomcatVisible:=mi.ReadBool('Common','TomcatVisible',true); + + Config.ASApache:=mi.ReadBool('Autostart','Apache',false); + Config.ASMySQL:=mi.ReadBool('Autostart','MySQL',false); + Config.ASFileZilla:=mi.ReadBool('Autostart','FileZilla',false); + Config.ASMercury:=mi.ReadBool('Autostart','Mercury',false); + Config.ASTomcat:=mi.ReadBool('Autostart','Tomcat',false); + + Config.UserConfig.Apache.DelimitedText:=mi.ReadString('UserConfigs','Apache',''); + Config.UserConfig.MySQL.DelimitedText:=mi.ReadString('UserConfigs','MySQL',''); + Config.UserConfig.FileZilla.DelimitedText:=mi.ReadString('UserConfigs','FileZilla',''); + Config.UserConfig.Mercury.DelimitedText:=mi.ReadString('UserConfigs','Mercury',''); + Config.UserConfig.Tomcat.DelimitedText:=mi.ReadString('UserConfigs','Tomcat',''); + + Config.UserLogs.Apache.DelimitedText:=mi.ReadString('UserLogs','Apache',''); + Config.UserLogs.MySQL.DelimitedText:=mi.ReadString('UserLogs','MySQL',''); + Config.UserLogs.FileZilla.DelimitedText:=mi.ReadString('UserLogs','FileZilla',''); + Config.UserLogs.Mercury.DelimitedText:=mi.ReadString('UserLogs','Mercury',''); + Config.UserLogs.Tomcat.DelimitedText:=mi.ReadString('UserLogs','Tomcat',''); + except + on e:exception do begin + MessageDlg(_('Error')+': '+E.Message,mtError,[mbOK],0); + end; + end; + mi.Free; +end; + +procedure SaveSettings; +var mi: TMemIniFile; +begin + mi:=nil; + try + mi:=TMemIniFile.Create(IniFileName); + mi.WriteString('Common','Editor',Config.EditorApp); + mi.WriteString('Common','Browser',Config.BrowserApp); + mi.WriteBool('Common','Debug',Config.ShowDebug); + mi.WriteInteger('Common','Debuglevel',Config.DebugLevel); + mi.WriteBool('Common','CheckDefaultPorts',Config.CheckDefaultPorts); + mi.WriteString('Common','Language',Config.Language); + mi.WriteBool('Common','TomcatVisible',Config.TomcatVisible); + + mi.WriteBool('Autostart','Apache',Config.ASApache); + mi.WriteBool('Autostart','MySQL',Config.ASMySQL); + mi.WriteBool('Autostart','FileZilla',Config.ASFileZilla); + mi.WriteBool('Autostart','Mercury',Config.ASMercury); + mi.WriteBool('Autostart','Tomcat',Config.ASTomcat); + + mi.WriteString('UserConfigs','Apache',Config.UserConfig.Apache.DelimitedText); + mi.WriteString('UserConfigs','MySQL',Config.UserConfig.MySQL.DelimitedText); + mi.WriteString('UserConfigs','FileZilla',Config.UserConfig.FileZilla.DelimitedText); + mi.WriteString('UserConfigs','Mercury',Config.UserConfig.Mercury.DelimitedText); + mi.WriteString('UserConfigs','Tomcat',Config.UserConfig.Tomcat.DelimitedText); + + mi.WriteString('UserLogs','Apache',Config.UserLogs.Apache.DelimitedText); + mi.WriteString('UserLogs','MySQL',Config.UserLogs.MySQL.DelimitedText); + mi.WriteString('UserLogs','FileZilla',Config.UserLogs.FileZilla.DelimitedText); + mi.WriteString('UserLogs','Mercury',Config.UserLogs.Mercury.DelimitedText); + mi.WriteString('UserLogs','Tomcat',Config.UserLogs.Tomcat.DelimitedText); + + mi.UpdateFile; + except + on e:exception do begin + MessageDlg(_('Error')+': '+E.Message,mtError,[mbOK],0); + end; + end; + mi.Free; +end; + + +function IsWindowsAdmin:boolean; +type + TIsUserAnAdminFunc = function (): BOOL; stdcall; +var + Shell32DLL: THandle; + IsUserAnAdminFunc: TIsUserAnAdminFunc; +begin + result:=true; + if WinVersion.Major<6 then exit; // older than vista? just return TRUE + + Shell32DLL := LoadLibrary('shell32.dll'); + try + if Shell32DLL <> 0 then begin + @IsUserAnAdminFunc := GetProcAddress(Shell32DLL, 'IsUserAnAdmin'); + if Assigned(@IsUserAnAdminFunc) then + result := IsUserAnAdminFunc(); + end; + except + end; + FreeLibrary(Shell32DLL); +end; + +function IntToServiceApp(b: boolean):string; +begin + if b then result:=_('Service') + else result:=_('Application') +end; + +function SystemErrorMessage(WinErrorCode: Cardinal): string; +var + P: PChar; +begin + if FormatMessage(Format_Message_Allocate_Buffer + Format_Message_From_System, + nil, + WinErrorCode, + 0, + @P, + 0, + nil) <> 0 then + begin + Result := trim(P); + LocalFree(Integer(P)); + end else begin + Result := ''; + end; +end; + +function GetCurrentUserName: string; +const + cnMaxUserNameLen = 254; +var + sUserName: string; + dwUserNameLen: DWORD; +begin + dwUserNameLen := cnMaxUserNameLen - 1; + SetLength(sUserName, cnMaxUserNameLen); + GetUserName(PChar(sUserName), dwUserNameLen); + SetLength(sUserName, dwUserNameLen); + Result := sUserName; +end; + + function GetWinVersion: TWinVersion; + var + osVerInfo: TOSVersionInfo; + s: string; + begin + osVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo) ; + if GetVersionEx(osVerInfo) then + begin + result.WinPlatForm:=osVerInfo.dwPlatformId; + result.Major:=osVerInfo.dwMajorVersion; + result.Minor:=osVerInfo.dwMinorVersion; + s:=osVerInfo.szCSDVersion; + if s<>'' then s:=' - '+s; + + result.WinVersion:=Format('%d.%d (build %d)%s',[osVerInfo.dwMajorVersion,osVerInfo.dwMinorVersion,osVerInfo.dwBuildNumber,s]); + end; + end; + + +function RunProcess(FileName: string; ShowCmd: DWORD; wait: Boolean; ProcID: PCardinal=nil): Longword; +var + StartupInfo: TStartupInfo; + ProcessInfo: TProcessInformation; +begin + FillChar(StartupInfo, SizeOf(StartupInfo), #0); + StartupInfo.cb := SizeOf(StartupInfo); + StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK; + StartupInfo.wShowWindow := ShowCmd; + if not CreateProcess(nil, + @Filename[1], + nil, + nil, + False, + CREATE_NEW_CONSOLE or + NORMAL_PRIORITY_CLASS, + nil, + nil, + StartupInfo, + ProcessInfo) + then + Result := WAIT_FAILED + else + begin + if wait = FALSE then + begin + if ProcID <> nil then ProcID^ := ProcessInfo.dwProcessId; + exit; + end; + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + GetExitCodeProcess(ProcessInfo.hProcess, Result); + end; + if ProcessInfo.hProcess <> 0 then + CloseHandle(ProcessInfo.hProcess); + if ProcessInfo.hThread <> 0 then + CloseHandle(ProcessInfo.hThread); +end; + +function ExecuteFile(FileName, Params, DefaultDir: string; ShowCmd: Integer): THandle; +var zFileName, zParams, zDir: array[0..255] of Char; +begin + if DefaultDir='' then DefaultDir:=BaseDir; + Result := ShellExecute(Application.MainForm.Handle, 'open', StrPCopy(zFileName, FileName), StrPCopy(zParams, Params), StrPCopy(zDir, DefaultDir), ShowCmd); +end; + +function RunAsAdmin(filename, parameters: string; ShowCmd: Integer): Cardinal; +var + Info: TShellExecuteInfo; + pInfo: PShellExecuteInfo; +begin + pInfo := @Info; + with Info do + begin + cbSize := SizeOf(Info); + fMask := SEE_MASK_NOCLOSEPROCESS; + wnd := application.Handle; + lpVerb := PChar('runas'); + lpFile := PChar(filename); + lpParameters := PChar(parameters + #0); + lpDirectory := NIL; + nShow := ShowCmd; + hInstApp := 0; + end; + if not ShellExecuteEx(pInfo) then begin + result:=GetLastError; + exit; + end; + {Wait to finish} + repeat + result := WaitForSingleObject(Info.hProcess, 500); + Application.ProcessMessages; + until (result <> WAIT_TIMEOUT); +end; + + +function Cardinal2IP(C: Cardinal):string; +begin + result:= + inttostr((c and $000000FF) )+'.'+ + inttostr((c and $0000FF00) shr 08)+'.'+ + inttostr((c and $00FF0000) shr 16)+'.'+ + inttostr((c and $FF000000) shr 24); +end; + +function GetComputerName:string; +const MAX_COMPUTERNAME_LENGTH=100; +var ComputerName: array[0..MAX_COMPUTERNAME_LENGTH + 1] of Char; +//var ComputerName: array[0..50 + 1] of Char; + size:cardinal; + LE: Cardinal; +begin + try + if CachedComputerName='' then begin + size:=MAX_COMPUTERNAME_LENGTH; + if Windows.GetComputerName(ComputerName, size) then begin + result:=ComputerName; + CachedComputerName:=ComputerName; + end else begin + LE:=GetLastError; + MessageDlg(Format('GetComputerName failed, Code: %d - %s',[LE,SystemErrorMessage(LE)]),mtError,[mbOK],0); + result:=''; + end; + end else begin + result:=CachedComputerName; + end; + except + result:=''; + end; +end; + + +function GetSystemLangShort:string; +var bla:array[0..1023] of Char; + s: string; + +begin + GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SENGLANGUAGE, @bla, sizeof(bla)); + s:=uppercase(bla); + if s='GERMAN' then result:='de' + else if s='ENGLISH' then result:='en' + else if s='FRENCH' then result:='fr' + else if s='ITALIAN' then result:='it' + else result:='en'; +end; + + +{ tConfig } + +constructor tConfig.Create; +begin + UserLogs.Apache:=TStringList.Create; + UserLogs.MySQL:=TStringList.Create; + UserLogs.FileZilla:=TStringList.Create; + UserLogs.Mercury:=TStringList.Create; + UserLogs.Tomcat:=TStringList.Create; + + UserConfig.Apache:=TStringList.Create; + UserConfig.MySQL:=TStringList.Create; + UserConfig.FileZilla:=TStringList.Create; + UserConfig.Mercury:=TStringList.Create; + UserConfig.Tomcat:=TStringList.Create; + + UserLogs.Apache.Delimiter:='|'; + UserLogs.MySQL.Delimiter:='|'; + UserLogs.FileZilla.Delimiter:='|'; + UserLogs.Mercury.Delimiter:='|'; + UserLogs.Tomcat.Delimiter:='|'; + + UserConfig.Apache.Delimiter:='|'; + UserConfig.MySQL.Delimiter:='|'; + UserConfig.FileZilla.Delimiter:='|'; + UserConfig.Mercury.Delimiter:='|'; + UserConfig.Tomcat.Delimiter:='|'; +end; + +destructor tConfig.Destroy; +begin + FreeAndNil(UserLogs.Apache); + FreeAndNil(UserLogs.MySQL); + FreeAndNil(UserLogs.FileZilla); + FreeAndNil(UserLogs.Mercury); + FreeAndNil(UserLogs.Tomcat); + + FreeAndNil(UserConfig.Apache); + FreeAndNil(UserConfig.MySQL); + FreeAndNil(UserConfig.FileZilla); + FreeAndNil(UserConfig.Mercury); + FreeAndNil(UserConfig.Tomcat); + inherited; +end; + + +initialization + Config:=tConfig.Create; + CachedComputerName:=''; + dfsVersionInfoResource1:=TdfsVersionInfoResource.Create(); + dfsVersionInfoResource1.Filename:=Application.ExeName; + with dfsVersionInfoResource1.FileVersion do + GlobalProgramversion:=IntToStr(Major)+'.'+IntToStr(Minor)+'.'+IntToStr(Release); + dfsVersionInfoResource1.Free; +finalization + Config.Free; +end. + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/xampp_control3.dpr b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/xampp_control3.dpr new file mode 100755 index 0000000000..cb07a0f28e --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel-beta-3/xampp_control3.dpr @@ -0,0 +1,63 @@ +program xampp_control3; + +uses + Forms, + ExtCtrls, + Controls, + Graphics, + uMain in 'uMain.pas' {fMain}, + uApache in 'uApache.pas', + uBaseModule in 'uBaseModule.pas', + uTools in 'uTools.pas', + uNetstatTable in 'uNetstatTable.pas', + uNetstat in 'uNetstat.pas' {fNetstat}, + uConfig in 'uConfig.pas' {fConfig}, + uMySQL in 'uMySQL.pas', + uFileZilla in 'uFileZilla.pas', + uMercury in 'uMercury.pas', + uProcesses in 'uProcesses.pas', + uHelp in 'uHelp.pas' {fHelp}, + uServices in 'uServices.pas', + gnugettext in 'gnugettext.pas', + uLanguage in 'uLanguage.pas' {fLanguage}, + VersInfo in 'VersInfo.pas', + uConfigUserDefined in 'uConfigUserDefined.pas' {fConfigUserDefined}, + uTomcat in 'uTomcat.pas'; + +{$R *.res} + +begin + AddDomainForResourceString ('delphi2006_de_en'); + AddDomainForResourceString('xampp_control'); + gnugettext.textdomain('xampp_control'); + + TP_GlobalIgnoreClassProperty(TControl,'HelpKeyword'); + TP_GlobalIgnoreClassProperty(TNotebook,'Pages'); + TP_GlobalIgnoreClassProperty(TControl,'ImeName'); + TP_GlobalIgnoreClass(TFont); + + IniFileName:=Application.ExeName; + IniFileName:=copy(IniFileName,1,length(IniFileName)-3)+'ini'; + + LoadSettings; + + if Config.Language='' then begin + Config.Language:=GetSystemLangShort; + Application.CreateForm(TfLanguage, fLanguage); + fLanguage.ShowModal; + fLanguage.Free; + SaveSettings; + end; + UseLanguage(Config.Language); + + + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TfMain, fMain); + Application.CreateForm(TfNetstat, fNetstat); + Application.CreateForm(TfConfig, fConfig); + Application.CreateForm(TfHelp, fHelp); + Application.CreateForm(TfLanguage, fLanguage); + Application.CreateForm(TfConfigUserDefined, fConfigUserDefined); + Application.Run; +end. diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/KILL.exe b/trunk/build/windows/xampp/src/xampp-control-panel/KILL.exe new file mode 100755 index 0000000000..e3779c1bab Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/KILL.exe differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/SERVICE.exe b/trunk/build/windows/xampp/src/xampp-control-panel/SERVICE.exe new file mode 100755 index 0000000000..d0a4490524 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/SERVICE.exe differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/SHOW.exe b/trunk/build/windows/xampp/src/xampp-control-panel/SHOW.exe new file mode 100755 index 0000000000..4675e72b0a Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/SHOW.exe differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/STOP.EXE b/trunk/build/windows/xampp/src/xampp-control-panel/STOP.EXE new file mode 100755 index 0000000000..701e3f86a1 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/STOP.EXE differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/StdAfx.h b/trunk/build/windows/xampp/src/xampp-control-panel/StdAfx.h new file mode 100755 index 0000000000..fcd1d32b2b --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/StdAfx.h @@ -0,0 +1,29 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__202939CE_5B83_453D_A1C9_63457E43CBB0__INCLUDED_) +#define AFX_STDAFX_H__202939CE_5B83_453D_A1C9_63457E43CBB0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +//#define STRICT +#define WIN32_LEAN_AND_MEAN +#include + +#ifdef UNICODE +#define stristr stristrW +#else +#define stristr stristrA +#endif + +char* __fastcall stristrA(const char* psz1, const char* psz2); +WCHAR* __fastcall stristrW(const WCHAR* pszMain, const WCHAR* pszSub); + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__202939CE_5B83_453D_A1C9_63457E43CBB0__INCLUDED_) diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/TRAY.ICO b/trunk/build/windows/xampp/src/xampp-control-panel/TRAY.ICO new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/TRAY.ICO differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.ICO b/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.ICO new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.ICO differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.INI b/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.INI new file mode 100755 index 0000000000..5fd60527e8 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.INI @@ -0,0 +1,19 @@ +[START] +hide = 1 +apache = 1 +mysql = 1 +tomcat = 1 + +[EXIT] +apache = 1 +mysql = 1 +tomcat = 1 + +[PORTS] +apache = 80 +mysql = 3306 +tomcat = 8080 + +[XAMPP] +setup = 1 + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.XML b/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.XML new file mode 100755 index 0000000000..f40d62fc46 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/XAMPP.XML @@ -0,0 +1,22 @@ + + + +XAMPP Control Utility + + + + + + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/ZX.BAK b/trunk/build/windows/xampp/src/xampp-control-panel/ZX.BAK new file mode 100755 index 0000000000..328df178f4 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/ZX.BAK @@ -0,0 +1,2 @@ +zip xampp.zip *.exe +move xampp.zip c:\ftproot diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/ZX.BAT b/trunk/build/windows/xampp/src/xampp-control-panel/ZX.BAT new file mode 100755 index 0000000000..58573beb86 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/ZX.BAT @@ -0,0 +1,2 @@ +zip c:\ftproot\xampp25.zip *.* + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp-icon.ico b/trunk/build/windows/xampp/src/xampp-control-panel/xampp-icon.ico new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/xampp-icon.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp.bmp b/trunk/build/windows/xampp/src/xampp-control-panel/xampp.bmp new file mode 100755 index 0000000000..92a87fc265 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/xampp.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.dsp b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.dsp new file mode 100755 index 0000000000..72b2cf514d --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.dsp @@ -0,0 +1,139 @@ +# Microsoft Developer Studio Project File - Name="xampp_control" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=xampp_control - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "xampp_control.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "xampp_control.mak" CFG="xampp_control - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "xampp_control - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "xampp_control - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "xampp_control - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0xc09 /d "NDEBUG" +# ADD RSC /l 0xc09 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /machine:I386 +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Cmds=copy release\xampp_control.exe C:\xampp1\xampp\xampp.exe copy release\xampp_control.exe C:\xampp2\xampp\xampp.exe copy release\xampp_control.exe xampp.exe +# End Special Build Tool + +!ELSEIF "$(CFG)" == "xampp_control - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0xc09 /d "_DEBUG" +# ADD RSC /l 0xc09 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Cmds=copy debug\xampp_control.exe C:\xampp1\xampp\xampp.exe +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "xampp_control - Win32 Release" +# Name "xampp_control - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\XAMPP.C +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\XAMPP.H +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\TRAY.ICO +# End Source File +# Begin Source File + +SOURCE=.\xampp.bmp +# End Source File +# Begin Source File + +SOURCE=.\xampp.gif +# End Source File +# Begin Source File + +SOURCE=.\XAMPP.ICO +# End Source File +# Begin Source File + +SOURCE=.\XAMPP.RC +# End Source File +# End Group +# End Target +# End Project diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.dsw b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.dsw new file mode 100755 index 0000000000..745b5769f4 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "xampp_control"=.\xampp_control.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.plg b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.plg new file mode 100755 index 0000000000..121609ad5e --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.plg @@ -0,0 +1,52 @@ + + +
    +

    Erstellungsprotokoll

    +

    +--------------------Konfiguration: xampp_control - Win32 Release-------------------- +

    +

    Befehlszeilen

    +Creating command line "rc.exe /l 0xc09 /fo"Release/XAMPP.res" /d "NDEBUG" "G:\xampp-dev\tomcat-control-panel\in-work\xampp-control-panel\XAMPP.RC"" +Erstellen der temporren Datei "c:\temp\RSP5C.tmp" mit Inhalten +[ +/nologo /ML /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Fp"Release/xampp_control.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c +"G:\xampp-dev\tomcat-control-panel\in-work\xampp-control-panel\XAMPP.C" +] +Creating command line "cl.exe @c:\temp\RSP5C.tmp" +Erstellen der temporren Datei "c:\temp\RSP5D.tmp" mit Inhalten +[ +kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /incremental:no /pdb:"Release/xampp_control.pdb" /machine:I386 /out:"Release/xampp_control.exe" +".\Release\XAMPP.OBJ" +".\Release\XAMPP.res" +] +Erstellen der Befehlzeile "link.exe @c:\temp\RSP5D.tmp" +

    Ausgabefenster

    +Ressourcen werden kompiliert... +Kompilierung luft... +XAMPP.C +G:\xampp-dev\tomcat-control-panel\in-work\xampp-control-panel\XAMPP.C(990) : warning C4013: 'GetJavaSDKState' undefiniert; Annahme: extern mit Rueckgabetyp int +G:\xampp-dev\tomcat-control-panel\in-work\xampp-control-panel\XAMPP.C(1396) : warning C4013: 'file_exists' undefiniert; Annahme: extern mit Rueckgabetyp int +G:\xampp-dev\tomcat-control-panel\in-work\xampp-control-panel\XAMPP.C(3304) : warning C4101: 'RegType' : Unreferenzierte lokale Variable +Linker-Vorgang luft... +Erstellen der temporren Datei "c:\temp\RSP5F.bat" mit Inhalten +[ +@echo off +copy release\xampp_control.exe C:\xampp1\xampp\xampp.exe +copy release\xampp_control.exe C:\xampp2\xampp\xampp.exe +copy release\xampp_control.exe xampp.exe +] +Erstellen der Befehlzeile "c:\temp\RSP5F.bat" + +Das System kann den angegebenen Pfad nicht finden. + 0 Datei(en) kopiert. +Das System kann den angegebenen Pfad nicht finden. + 0 Datei(en) kopiert. + 1 Datei(en) kopiert. + + + +

    Ergebnisse

    +xampp_control.exe - 0 Fehler, 3 Warnung(en) +
    + + diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.sln b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.sln new file mode 100755 index 0000000000..93489dcc33 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.sln @@ -0,0 +1,23 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xampp_control", "xampp_control.vcproj", "{038C5A0C-969D-4F21-8CE6-358CAABC0A37}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Debug.ActiveCfg = Debug|Win32 + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Debug.Build.0 = Debug|Win32 + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Release.ActiveCfg = Release|Win32 + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.suo b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.suo new file mode 100755 index 0000000000..97e2a061a4 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.suo differ diff --git a/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.vcproj b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.vcproj new file mode 100755 index 0000000000..0bbf8b5fab --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-control-panel/xampp_control.vcproj @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/icons/xampp-icon-uninstall.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/icons/xampp-icon-uninstall.ico new file mode 100755 index 0000000000..15043400a9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/icons/xampp-icon-uninstall.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/icons/xampp-icon.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/icons/xampp-icon.ico new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/icons/xampp-icon.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/perl-addon.nsi b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/perl-addon.nsi new file mode 100755 index 0000000000..c48d9db650 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/perl-addon.nsi @@ -0,0 +1,144 @@ +; Author: Kay Vogelgesang for ApacheFriends XAMPP win32 + +SetCompressor /SOLID lzma +XPStyle on + +RequestExecutionLevel admin + +; HM NIS Edit Wizard helper defines +!define PRODUCT_NAME "(Mod) Perl Addon" +!define PRODUCT_VERSION "5.10.0 (Apache 2.2.11)" +!define PRODUCT_PUBLISHER "Kay Vogelgesang, Kai Oswald Seidler, ApacheFriends" +!define PRODUCT_WEB_SITE "http://www.apachefriends.org" + +; MUI 1.67 compatible ------ +!include "MUI.nsh" +BGGradient f87820 FFFFFF FFFFFF +InstallColors FF8080 000030 +CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp" + +; MUI Settings +!define MUI_ABORTWARNING +!define MUI_ICON "C:\xamppdev\NSI\icons\xampp-icon.ico" +!define MUI_UNICON "C:\xamppdev\NSI\icons\xampp-icon-uninstall.ico" +!define MUI_WELCOMEPAGE +!define MUI_CUSTOMPAGECOMMANDS +!define MUI_COMPONENTSPAGE +!define MUI_COMPONENTSPAGE_NODESC + +; Welcome page +!insertmacro MUI_PAGE_WELCOME +; License page +; !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\license.txt" +; Directory page +!insertmacro MUI_PAGE_DIRECTORY +; Instfiles page +!insertmacro MUI_PAGE_INSTFILES +; Finish page +!insertmacro MUI_PAGE_FINISH +; Default LANGUAGE +;!insertmacro MUI_LANGUAGE "German" + + !insertmacro MUI_LANGUAGE "English" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Catalan" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Indonesian" + + !insertmacro MUI_RESERVEFILE_LANGDLL +; MUI end ------ + +Function .onInit +!insertmacro MUI_LANGDLL_DISPLAY + + ReadRegStr $INSTDIR HKLM "Software\xampp" "Install_Dir" + StrCmp $INSTDIR "" 0 NoAbort + MessageBox MB_OK "XAMPP win32 not found. Unable to get install path." + Abort ; causes installer to quit. + NoAbort: + ReadRegStr $1 HKLM "Software\xampp" "apache" + StrCmp $1 "2211" NoMessage + MessageBox MB_YESNO "Warning: Cannot found Apache httpd 2.2.11 for mod_perl (recommended)! Continue?" IDNO NoMessage + Abort + NoMessage: + + ReadRegStr $8 HKLM "Software\xampp" "perl" + StrCmp $8 "" 0 isPerl + Goto newPerl + isPerl: + IntCmp $8 5100001 is51 lessthan51 morethan51 + is51: + MessageBox MB_OKCANCEL "Found XAMPP Perl Add-On! Try to upgrade this Add-On?" IDOK true IDCANCEL false + false: + abort + true: + Goto newPerl + lessthan51: + morethan51: + MessageBox MB_OK "Sorry, but no upgrade possible with your old Add-On (Perl version $8)!" + abort + newPerl: + +FunctionEnd + + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +Icon "C:\xamppdev\NSI\icons\xampp-icon.ico" +OutFile "C:\xamppdev\NSI\output\xampp-win32-perl-addon-5.10.0-2.2.11-pl2-installer.exe" +InstallDir $INSTDIR +ShowInstDetails show + + +Section "Hauptgruppe" SEC01 +SectionIn RO + ReadRegStr $8 HKLM "Software\xampp" "perl" + + SetOutPath $INSTDIR + SetOverwrite on + File /r "H:\xamppdev\installer\xampp-win32-perl-addon-5.10.0-2.2.11-pl2\*.*" + WriteRegStr HKLM "Software\xampp" "perl" "510000101" + WriteRegStr HKLM "Software\xampp" "modperl" "204000" + ExecWait '"$INSTDIR\php\php.exe" -n -d output_buffering=0 "$INSTDIR\install\install.php"' $9 + + # WriteRegStr HKLM "Software\xampp" "perl" "5100001" + # WriteRegStr HKLM "Software\xampp" "modperl" "203000" + # ExecWait '"$INSTDIR\php\php.exe" -n -d output_buffering=0 "$INSTDIR\install\install.php"' $4 +SectionEnd + + + + diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/tomcat-addon.nsi b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/tomcat-addon.nsi new file mode 100755 index 0000000000..66af890b45 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/tomcat-addon.nsi @@ -0,0 +1,155 @@ +; Author: Kay Vogelgesang for ApacheFriends XAMPP win32 + +SetCompressor /solid lzma +XPStyle on + +RequestExecutionLevel admin + +; HM NIS Edit Wizard helper defines +!define PRODUCT_NAME "Tomcat Addon pl1" +!define PRODUCT_VERSION "6.0.18 (mod_jk/1.2.27)" +!define PRODUCT_PUBLISHER "Kay Vogelgesang, Kai Oswald Seidler, ApacheFriends" +!define PRODUCT_WEB_SITE "http://www.apachefriends.org" + +; MUI 1.67 compatible ------ +!include "MUI.nsh" +BGGradient f87820 FFFFFF FFFFFF +InstallColors FF8080 000030 +CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp" + +; MUI Settings +!define MUI_ABORTWARNING +!define MUI_ICON "C:\xamppdev\NSI\icons\xampp-icon.ico" +!define MUI_UNICON "C:\xamppdev\NSI\icons\xampp-icon-uninstall.ico" +!define MUI_WELCOMEPAGE +!define MUI_CUSTOMPAGECOMMANDS +!define MUI_COMPONENTSPAGE +!define MUI_COMPONENTSPAGE_NODESC + +; Welcome page +!insertmacro MUI_PAGE_WELCOME +; License page +; !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\license.txt" +; Directory page +!insertmacro MUI_PAGE_DIRECTORY +; Instfiles page +!insertmacro MUI_PAGE_INSTFILES +; Finish page +!insertmacro MUI_PAGE_FINISH +; Default LANGUAGE +;!insertmacro MUI_LANGUAGE "German" + + + !insertmacro MUI_LANGUAGE "English" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Catalan" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_RESERVEFILE_LANGDLL +; MUI end ------ + + + + +Function .onInit +!insertmacro MUI_LANGDLL_DISPLAY + ReadRegStr $INSTDIR HKLM "Software\xampp" "Install_Dir" + StrCmp $INSTDIR "" 0 NoAbort + MessageBox MB_OK "XAMPP win32 not found. Unable to get install path." + Abort ; causes installer to quit. + NoAbort: + + ReadRegStr $1 HKLM "Software\xampp" "apache" + IntCmp $1 2211 is2211 lessthan2211 morethan2211 + lessthan2211: + morethan2211: + MessageBox MB_OK "In this package mod_jk/1.2.27 is only tested with apache version 2.2.11. It seems you a using a lower version!" + MessageBox MB_OKCANCEL "Do you want to continue this installation?" IDOK true IDCANCEL false + false: + Abort + true: + is2211: + + ReadRegStr $2 HKLM "Software\xampp" "java" + StrCmp $2 "" NoAddon 0 + MessageBox MB_OK "XAMPP Java Add-on is installed already!" + Abort ; causes installer to quit. + NoAddon: + + ReadRegStr $4 HKLM "Software\JavaSoft\Java Development Kit\1.5" "JavaHome" + StrCmp $4 "" 0 YesJava + ReadRegStr $5 HKLM "Software\JavaSoft\Java Development Kit\1.6" "JavaHome" + StrCmp $5 "" 0 YesJava + ReadRegStr $6 HKLM "SOFTWARE\Sun Microsystems\Application Server\9PE" "INSTALLPATH" + StrCmp $6 "" 0 YesJava + + MessageBox MB_OK "SUN Java 5 or 6 SDK win32 not found. Unable to get install path." + Abort ; causes installer to quit. + YesJava: + FunctionEnd + + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +Icon "C:\xamppdev\NSI\icons\xampp-icon.ico" +OutFile "C:\xamppdev\NSI\output\xampp-win32-tomcat-addon-6.0.18-2.2.11-pl1-installer.exe" + +InstallDir $INSTDIR +ShowInstDetails show + +Section "Hauptgruppe" SEC01 +SectionIn RO +WriteRegStr HKLM "Software\xampp" "java" "1227" + SetOutPath $INSTDIR + SetOverwrite on + File /r "H:\xamppdev\installer\xampp-win32-tomcat-addon-6.0.18-2.2.11-pl1\*.*" + ;WriteINIStr "$INSTDIR\javapath.ini" "JavaPath" "JavaHome" $3 + ;ExecWait '"$INSTDIR\install\phpcli.exe" -n -d output_buffering=0 "$INSTDIR\install\javaaddon.php"' $9 + ExecWait '"$INSTDIR\php\php.exe" -n -d output_buffering=0 "$INSTDIR\install\install.php"' $9 +SectionEnd + +; Section "Start Menu Shortcuts" +Function .onInstSuccess +ReadRegStr $1 HKLM "Software\xampp" "programfiles" +StrCmp $1 "0" no_pfiles + CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\Tomcat start.lnk" "$INSTDIR\tomcat_start.bat" "" "$INSTDIR\install\tomcat.ico" + CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\Tomcat stop.lnk" "$INSTDIR\tomcat_stop.bat" "" "$INSTDIR\install\tomcatstop.ico" +no_pfiles: +FunctionEnd + + +;SectionEnd + diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-german.ini b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-german.ini new file mode 100755 index 0000000000..d401499e04 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-german.ini @@ -0,0 +1,80 @@ +[Settings] +NumFields=9 + +[Field 1] +Type=label +Text=XAMPP DESKTOP +Left=40 +Right=-1 +Top=0 +Bottom=10 + +[Field 2] +Type=checkbox +Text=XAMPP als Desktop-Icon +Left=40 +Right=-1 +Top=10 +Bottom=25 +State=1 + +[Field 3] +Type=label +Text=XAMPP START MEN +Left=40 +Right=-1 +Top=30 +Bottom=40 + +[Field 4] +Type=checkbox +Text=Apache Friends XAMPP Eintrag unter Start/Programme +Left=40 +Right=-1 +Top=40 +Bottom=55 +State=1 + +[Field 5] +Type=label +Text=DIENSTE +Left=40 +Right=-1 +Top=60 +Bottom=70 + +[Field 6] +Type=checkbox +Text=Installation des Apache2 als Dienst +Left=40 +Right=-1 +Top=72 +Bottom=82 +State=0 + +[Field 7] +Type=checkbox +Text=Installation von MySQL als Dienst +Left=40 +Right=-1 +Top=82 +Bottom=92 +State=0 + +[Field 8] +Type=checkbox +Text=Installation des Filezilla FTPD als Dienst +Left=40 +Right=-1 +Top=92 +Bottom=102 +State=0 + +[Field 9] +Type=link +Left=40 +Right=-1 +Top=115 +Bottom=125 +State=http://www.apachefriends.org/en/faq-xampp-windows.html +Text=Bitte besuchen Sie auch die XAMPP Windows FAQ Website diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-japanese.ini b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-japanese.ini new file mode 100755 index 0000000000..a5b915fdf5 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-japanese.ini @@ -0,0 +1,80 @@ +[Settings] +NumFields=9 + +[Field 1] +Type=label +Text=XAMPP fXNgbv +Left=40 +Right=-1 +Top=0 +Bottom=10 + +[Field 2] +Type=checkbox +Text=fXNgbvXamppACR쐬 +Left=40 +Right=-1 +Top=10 +Bottom=25 +State=1 + +[Field 3] +Type=label +Text=XAMPP X^[gj[ +Left=40 +Right=-1 +Top=30 +Bottom=40 + +[Field 4] +Type=checkbox +Text=X^[gj[Apache Friends XAMPP tH_[쐬 +Left=40 +Right=-1 +Top=40 +Bottom=55 +State=1 + +[Field 5] +Type=label +Text=T[rX +Left=40 +Right=-1 +Top=60 +Bottom=70 + +[Field 6] +Type=checkbox +Text=ApacheT[rXƂăCXg[ +Left=40 +Right=-1 +Top=72 +Bottom=82 +State=0 + +[Field 7] +Type=checkbox +Text=MySQLT[rXƂăCXg[ +Left=40 +Right=-1 +Top=82 +Bottom=92 +State=0 + +[Field 8] +Type=checkbox +Text=FileZillaT[rXƂăCXg[ +Left=40 +Right=-1 +Top=92 +Bottom=102 +State=0 + +[Field 9] +Type=link +Left=40 +Right=-1 +Top=115 +Bottom=125 +State=http://www.apachefriends.org/en/faq-xampp-windows.html +Text=XAMPP WindowsłFAQ y[W diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-upgrade.nsi b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-upgrade.nsi new file mode 100755 index 0000000000..d0dfe59680 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-upgrade.nsi @@ -0,0 +1,171 @@ +; Author: Kay Vogelgesang for ApacheFriends XAMPP win32 + +SetCompressor /solid lzma +XPStyle on +SilentInstall normal + +RequestExecutionLevel admin + +; HM NIS Edit Wizard helper defines +!define PRODUCT_NAME "XAMPP Upgrade" +!define PRODUCT_VERSION "1.7.1" +!define PRODUCT_PUBLISHER "Kay Vogelgesang, Kai Oswald Seidler, ApacheFriends" +!define PRODUCT_WEB_SITE "http://www.apachefriends.org" + +; MUI 1.67 compatible ------ +!include "MUI.nsh" +BGGradient f87820 FFFFFF FFFFFF +InstallColors FF8080 000030 +CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp" + +; MUI Settings +!define MUI_ABORTWARNING +!define MUI_ICON "C:\xamppdev\NSI\icons\xampp-icon.ico" +!define MUI_UNICON "C:\xamppdev\NSI\icons\xampp-icon-uninstall.ico" +!define MUI_WELCOMEPAGE +!define MUI_CUSTOMPAGECOMMANDS +!define MUI_COMPONENTSPAGE +!define MUI_COMPONENTSPAGE_NODESC + +; Welcome page +!insertmacro MUI_PAGE_WELCOME +; License page +; !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\license.txt" +; Directory page +!insertmacro MUI_PAGE_DIRECTORY +; Instfiles page +!insertmacro MUI_PAGE_INSTFILES +; Finish page +!insertmacro MUI_PAGE_FINISH +; Default LANGUAGE +;!insertmacro MUI_LANGUAGE "German" + + !insertmacro MUI_LANGUAGE "English" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Catalan" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Indonesian" + + !insertmacro MUI_RESERVEFILE_LANGDLL +; MUI end ------ + +Function .onInit +SetSilent normal +!insertmacro MUI_LANGDLL_DISPLAY + ReadRegStr $INSTDIR HKLM "Software\xampp" "Install_Dir" + StrCmp $INSTDIR "" 0 NoAbort + MessageBox MB_OK "XAMPP win32 not found. Unable to get install path." + Abort ; causes installer to quit. + NoAbort: + ReadRegStr $2 HKLM "Software\xampp" "version" + StrCmp $2 "1700" NoVersionAbort + MessageBox MB_OK "Need XAMPP version 1.7.0 but cannot find this version. Stop installation." + Abort ; causes no correct version found + NoVersionAbort: + + ; ReadRegStr $2 HKLM "Software\xampp" "apache" + ; StrCmp $2 "2240" NoversionAbort + ; MessageBox MB_OK "Need XAMPP with Apache 2.2.4 for this Update!" + ; Abort ; causes installer to quit. + ; NoversionAbort: + + ReadRegStr $R0 HKCU "Control Panel\International" Locale + StrCmp $R0 "00000407" detection_de + GOTO no_de + detection_de: + MessageBox MB_OK "Bitte alle Apache, MySQL, FileZilla, Mercury Prozesse im XAMPP vor dem Upgrading beenden. Diese Prozesse knnen nach dem Upgrade wieder gestartet werden." + GOTO end_box + no_de: + MessageBox MB_OK "Please stop all Apache, MySQL, FileZilla, Mercury processes in XAMPP before upgrading! After upgrade you can (re)start these services." + end_box: +FunctionEnd + + +;Section desktopIcon +;Section "XAMPP Desktop" desktopIcon +;CreateShortCut "$DESKTOP\XAMPP Control Panel.lnk" "$INSTDIR\xampp-control.exe" "" +;SectionEnd + + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +Icon "C:\xamppdev\NSI\icons\xampp-icon.ico" +OutFile "C:\xamppdev\NSI\output\xampp-win32-upgrade-1.7.0-1.7.1-installer.exe" +; InstallDir "$PROGRAMFILES" + +InstallDir $INSTDIR +ShowInstDetails show + + +Section "Hauptgruppe" SEC01 + AllowSkipFiles on + SetOutPath $INSTDIR + SetOverwrite on + File /r "H:\xamppdev\installer\xampp-win32-upgrade\*.*" + ; ExecWait '"$INSTDIR\php\php.exe" -n -d output_buffering=0 "$INSTDIR\install\upgrade.php"' $4 +WriteRegStr HKLM "Software\xampp" "version" "1710" +WriteRegStr HKLM "Software\xampp" "apache" "2211" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xampp" "DisplayName" "XAMPP ${PRODUCT_VERSION}" +SectionEnd + + +Function .onInstSuccess +ReadRegStr $4 HKLM "Software\xampp" "lang" +Delete "$INSTDIR\xampp-control.exe" +StrCmp $4 "1041" japanese +Delete "$INSTDIR\xampp-control-jp.exe" +Rename "$INSTDIR\xampp-control-default.exe" "$INSTDIR\xampp-control.exe" +Goto xamppcontrol_out +japanese: +Delete "$INSTDIR\xampp-control-default.exe" +Rename "$INSTDIR\xampp-control-jp.exe" "$INSTDIR\xampp-control.exe" +xamppcontrol_out: +FunctionEnd + +;Section "Start Menu Shortcuts" +;Delete "$SMPROGRAMS\apachefriends\xampp\*.*" +;RMDir "$SMPROGRAMS\apachefriends\xampp" +;RMDir "$SMPROGRAMS\apachefriends" +;CreateDirectory "$SMPROGRAMS\apachefriends\xampp" +; CreateShortCut "$SMPROGRAMS\apachefriends\xampp" "" "" +; CreateShortCut "$SMPROGRAMS\apachefriends\xampp\CONTROL XAMPP SERVER PANEL.lnk" "$INSTDIR\xampp-control.exe" "" "$INSTDIR\install\xampp.ico" +; CreateShortCut "$SMPROGRAMS\apachefriends\xampp\xampp httpdoc folder.lnk" "$INSTDIR\htdocs" "" "$INSTDIR\install\folder.ico" +; CreateShortCut "$SMPROGRAMS\apachefriends\xampp\port checking.lnk" "$INSTDIR\xampp-portcheck.exe" "" "$INSTDIR\install\xamppcontrol.ico" +; CreateShortCut "$SMPROGRAMS\apachefriends\xampp\php switch.lnk" "$INSTDIR\php-switch.bat" "" "$INSTDIR\install\php.ico" +; CreateShortCut "$SMPROGRAMS\apachefriends\xampp\xampp uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\install\xampp-icon-uninstall.ico" +;SectionEnd + + diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-usb-lite.nsi b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-usb-lite.nsi new file mode 100755 index 0000000000..e67a3ac946 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp-usb-lite.nsi @@ -0,0 +1,156 @@ +; Author: Kay Vogelgesang for ApacheFriends XAMPP win32 + +;--------------------- +;Include Modern UI + !include "MUI.nsh" +;-------------------------------- + +SetCompressor /solid lzma +XPStyle on +; HM NIS Edit Wizard helper defines + !define PRODUCT_NAME "XAMPP (USB)" + !define PRODUCT_VERSION "1.7.5" + !define PRODUCT_PUBLISHER "Kay Vogelgesang, Kai Oswald Seidler, ApacheFriends" + !define PRODUCT_WEB_SITE "http://www.apachefriends.org" +Caption "XAMPP USB & Lite ${PRODUCT_VERSION} win32" +; InstallDirRegKey HKCU "Software\xampp" "" +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +OutFile "F:\xampp-dev\xampp-win32-${PRODUCT_VERSION}-usb-lite.exe" +;Vista redirects $SMPROGRAMS to all users without this +RequestExecutionLevel admin +BGGradient f87820 FFFFFF FFFFFF +InstallColors FF8080 000030 +CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp" + +;-------------------------------- + +; MUI Settings + !define MUI_ABORTWARNING + !define MUI_ICON "C:\xampp\src\xampp-nsi-installer\icons\xampp-icon.ico" + !define MUI_WELCOMEPAGE + !define MUI_CUSTOMPAGECOMMANDS + !define MUI_COMPONENTSPAGE + !define MUI_COMPONENTSPAGE_NODESC + +; Welcome page + !insertmacro MUI_PAGE_WELCOME +; Components +; !insertmacro MUI_PAGE_COMPONENTS +; License page +;!insertmacro MUI_PAGE_LICENSE "${NSISDIR}\license.txt" +; Directory page +; Instfiles page + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES +; Finish page + !insertmacro MUI_PAGE_FINISH + +;-------------------------------- + +;Languages + + !insertmacro MUI_LANGUAGE "English" # first language is the default language + !insertmacro MUI_LANGUAGE "German" + ; !insertmacro MUI_LANGUAGE "Japanese" + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "xampp.ini" + ; ReserveFile "xampp-japanese.ini" + ReserveFile "xampp_home.ini" + ; ReserveFile "xampp_home-japanese.ini" + ReserveFile "xampp-german.ini" + ReserveFile "xampp_home-german.ini" + + !insertmacro MUI_RESERVEFILE_LANGDLL + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + +;-------------------------------- +;Variables + + Var INI_VALUE + Var INI_VALUE2 + Var INI_VALUE3 + Var INI_VALUE4 + Var INI_VALUE5 + Var INST_MESS + Var INST_MESS1 + Var INST_MESS2 + Var INST_MESS3 + Var INST_MESS4 + Var MESS_INSTDIR1 + Var MESS_INSTDIR2 + Var FTP_INSTALL + Var MAIL_INSTALL + Var DB_DEL + Var NO_DEL + +InstallDir "c:\xampp" +Icon "C:\xampp\src\xampp-nsi-installer\icons\xampp-icon.ico" +ShowInstDetails show + + +Section "XAMPP Files" SEC01 +SetOutPath "$INSTDIR" +SetOverwrite ifnewer +File /r "F:\release175\release_rc2\xampp-win32-1.7.5-usb-lite\xampp\*.*" +ExecWait '"$INSTDIR\php\php.exe" -n -d output_buffering=0 "$INSTDIR\install\install.php" usb' $4 + +SectionEnd + +; --------------------------------------- + +Function .onInit + !insertmacro MUI_LANGDLL_DISPLAY + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp.ini" + ; !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp-japanese.ini" + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp_home.ini" + ; !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp_home-japanese.ini" + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp-german.ini" + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp_home-german.ini" + + ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $R1 "6.0" detection_VISTA + StrCmp $R1 "6.1" detection_VISTA + Goto no_vista + detection_VISTA: + ReadRegStr $R2 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" EnableLUA + ;ReadRegStr $R0 HKCU "Control Panel\International" Locale + ;StrCmp $R0 "00000407" detection_de + StrCmp $LANGUAGE "1031" detection_de + GOTO no_de + detection_de: + StrCmp $R2 "1" IS_UACDE + MessageBox MB_OK "Die Windows Vista Benutzerkontensteuerung (UAC) ist auf Ihrem System deaktiviert (empfohlen!). Bitte beachten Sie, das eine nachtrgliche Aktivierung des Benutzerkontenschutz die Funktionalitt der XAMPP-Komponenten beeintrchtigen kann." + GOTO ISNO_UACDE + IS_UACDE: + MessageBox MB_OK "Wichtige MS Vista Warnung! Aufgrund der aktivierten Benutzerkontensteuerung (UAC) auf Ihrem System sind XAMPP-Komponenten und Funktionen ggf. nur eingeschrnkt einsetzbar. Vermeiden Sie in diesem Fall die Installation von XAMPP unter $PROGRAMFILES oder deaktivieren Sie den Benutzerkontensteuerung ber msconfig nach diesem Setup." + ISNO_UACDE: + GOTO no_vista + no_de: + StrCmp $R2 "1" IS_UACE + MessageBox MB_OK "Windows Vista User Account Control (UAC) is deactivated on your system (recommended!). Please consider: A later activation of UAC can restrict the functionality of XAMPP!" + GOTO no_vista + IS_UACE: + MessageBox MB_OK "Important MS Vista Note! Because an activated Windows Vista User Account Control (UAC) on your sytem some functions of xampp are possibly restricted. With UAC please avoid to install XAMPP to $PROGRAMFILES (because of not enough write permisssions). Or deactivate UAC (with msconfig) after this Setup." + no_vista: + + ;ReadRegStr $R0 HKCU "Control Panel\International" Locale + StrCmp $LANGUAGE "1031" detect_de + GOTO non_de + detect_de: + MessageBox MB_OK "Bitte whlen Sie im nchsten Schritt ihren Wechseldatentrger z.B. USB Stick aus. Alternativ knnen Sie diese Version auch als XAMPP Lite auf ihre Festplatte installieren." + GOTO usb_end + non_de: + MessageBox MB_OK "In the next step please choice your usb device for installation. Alternate you can take your hard disk for a lite installation." + usb_end: + + +FunctionEnd + + diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp.ini b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp.ini new file mode 100755 index 0000000000..1fbc297e4f --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp.ini @@ -0,0 +1,80 @@ +[Settings] +NumFields=9 + +[Field 1] +Type=label +Text=XAMPP DESKTOP +Left=40 +Right=-1 +Top=0 +Bottom=10 + +[Field 2] +Type=checkbox +Text=Create a XAMPP desktop icon +Left=40 +Right=-1 +Top=10 +Bottom=25 +State=1 + +[Field 3] +Type=label +Text=XAMPP START MENU +Left=40 +Right=-1 +Top=30 +Bottom=40 + +[Field 4] +Type=checkbox +Text=Create an Apache Friends XAMPP folder in the start menu +Left=40 +Right=-1 +Top=40 +Bottom=55 +State=1 + +[Field 5] +Type=label +Text=SERVICE SECTION +Left=40 +Right=-1 +Top=60 +Bottom=70 + +[Field 6] +Type=checkbox +Text=Install Apache as service +Left=40 +Right=-1 +Top=72 +Bottom=82 +State=0 + +[Field 7] +Type=checkbox +Text=Install MySQL as service +Left=40 +Right=-1 +Top=82 +Bottom=92 +State=0 + +[Field 8] +Type=checkbox +Text=Install Filezilla as service +Left=40 +Right=-1 +Top=92 +Bottom=102 +State=0 + +[Field 9] +Type=link +Left=40 +Right=-1 +Top=115 +Bottom=125 +State=http://www.apachefriends.org/en/faq-xampp-windows.html +Text=See also the XAMPP for Windows FAQ Page diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp.nsi b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp.nsi new file mode 100755 index 0000000000..ab0800fb77 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp.nsi @@ -0,0 +1,594 @@ +; Author: Kay Vogelgesang for ApacheFriends XAMPP win32 + +;--------------------- +;Include Modern UI + !include "MUI.nsh" +;-------------------------------- + +SetCompressor /solid lzma +XPStyle on +; HM NIS Edit Wizard helper defines + !define PRODUCT_NAME "XAMPP" + !define PRODUCT_VERSION "1.7.5" + !define PRODUCT_PUBLISHER "Kay Vogelgesang, Kai Oswald Seidler, ApacheFriends" + !define PRODUCT_WEB_SITE "http://www.apachefriends.org" +Caption "XAMPP ${PRODUCT_VERSION} win32" +InstallDirRegKey HKLM "Software\xampp" "Install_Dir" +; InstallDirRegKey HKCU "Software\xampp" "" +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +OutFile "F:\xampp-dev\xampp-win32-${PRODUCT_VERSION}-VC9-installer.exe" +;Vista redirects $SMPROGRAMS to all users without this +RequestExecutionLevel admin +BGGradient f87820 FFFFFF FFFFFF +InstallColors FF8080 000030 +CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp" + +;-------------------------------- +;Language Selection Dialog Settings + +;Remember the installer language + !define MUI_LANGDLL_REGISTRY_ROOT "HKLM" + !define MUI_LANGDLL_REGISTRY_KEY "Software\xampp" + !define MUI_LANGDLL_REGISTRY_VALUENAME "lang" + +;-------------------------------- + +; MUI Settings + !define MUI_ABORTWARNING + !define MUI_ICON "C:\xampp\src\xampp-nsi-installer\icons\xampp-icon.ico" + !define MUI_UNICON "C:\xampp\src\xampp-nsi-installer\icons\xampp-icon-uninstall.ico" + !define MUI_WELCOMEPAGE + !define MUI_CUSTOMPAGECOMMANDS + !define MUI_COMPONENTSPAGE + !define MUI_COMPONENTSPAGE_NODESC + +; Welcome page + !insertmacro MUI_PAGE_WELCOME +; Components +; !insertmacro MUI_PAGE_COMPONENTS +; License page +;!insertmacro MUI_PAGE_LICENSE "${NSISDIR}\license.txt" +; Directory page + !insertmacro MUI_PAGE_DIRECTORY + Page custom CustomPageC +; Instfiles page + !insertmacro MUI_PAGE_INSTFILES +; Finish page + !insertmacro MUI_PAGE_FINISH + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- + +;Languages + + !insertmacro MUI_LANGUAGE "English" # first language is the default language + !insertmacro MUI_LANGUAGE "German" + ; !insertmacro MUI_LANGUAGE "Japanese" + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "xampp.ini" + ; ReserveFile "xampp-japanese.ini" + ReserveFile "xampp_home.ini" + ; ReserveFile "xampp_home-japanese.ini" + ReserveFile "xampp-german.ini" + ReserveFile "xampp_home-german.ini" + + !insertmacro MUI_RESERVEFILE_LANGDLL + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + +;-------------------------------- +;Variables + + Var INI_VALUE + Var INI_VALUE2 + Var INI_VALUE3 + Var INI_VALUE4 + Var INI_VALUE5 + Var INST_MESS + Var INST_MESS1 + Var INST_MESS2 + Var INST_MESS3 + Var INST_MESS4 + Var MESS_INSTDIR1 + Var MESS_INSTDIR2 + Var DB_DEL + Var NO_DEL + +InstallDir "c:\xampp" +Icon "C:\xampp\src\xampp-nsi-installer\icons\xampp-icon.ico" +UninstallIcon "C:\xampp\src\xampp-nsi-installer\icons\xampp-icon-uninstall.ico" +ShowInstDetails show +ShowUninstDetails show + +Section "XAMPP Files" SEC01 +SetOutPath "$INSTDIR" +SetOverwrite ifnewer +File /r "F:\release175\release_rc2\xampp\*.*" +ExecWait '"$INSTDIR\php\php.exe" -n -d output_buffering=0 "$INSTDIR\install\install.php"' $4 + +WriteRegStr HKLM "Software\xampp" "Install_Dir" "$INSTDIR" +WriteRegStr HKLM "Software\xampp" "apache" "2217" +WriteRegStr HKLM "Software\xampp" "version" "1750" +WriteRegStr HKLM "Software\xampp" "apacheservice" "0" +WriteRegStr HKLM "Software\xampp" "mysqlservice" "0" +WriteRegStr HKLM "Software\xampp" "tomcatservice" "0" +WriteRegStr HKLM "Software\xampp" "filezillainstall" "1" +WriteRegStr HKLM "Software\xampp" "filezillaservice" "0" +WriteRegStr HKLM "Software\xampp" "mercuryinstall" "1" +WriteRegStr HKLM "Software\xampp" "addonperl" "1" +WriteRegStr HKLM "Software\xampp" "addonpython" "0" +WriteRegStr HKLM "Software\xampp" "addontomcat" "1" +WriteRegStr HKLM "Software\xampp" "addoncocoon" "0" +WriteRegStr HKLM "Software\xampp" "programfiles" "0" +WriteRegStr HKLM "Software\xampp" "desktopicon" "0" +WriteRegStr HKLM "Software\xampp" "services" "0" +WriteRegStr HKLM "Software\xampp" "lang" "$LANGUAGE" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xampp" "DisplayName" "${PRODUCT_NAME} ${PRODUCT_VERSION}" +WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xampp" "UninstallString" '"$INSTDIR\uninstall.exe"' +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xampp" "NoModify" 1 +WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xampp" "NoRepair" 1 +WriteUninstaller "$INSTDIR\Uninstall.exe" + +ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $R0 "" 0 NTsec + MessageBox MB_OK "No Service Installation avaible on Windows 98/ME/Home" + ; StrCmp $LANGUAGE "1041" japanese1 + StrCmp $LANGUAGE "1031" german1 + ;Read a value from an InstallOptions INI file + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE "xampp_home.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE2 "xampp_home.ini" "Field 4" "State" + Goto no_srv + german1: + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE "xampp_home-german.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE2 "xampp_home-german.ini" "Field 4" "State" + Goto no_srv + ; japanese1: + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE "xampp_home-japanese.ini" "Field 2" "State" + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE2 "xampp_home-japanese.ini" "Field 4" "State" + ; Goto no_srv +NTsec: +; FULL SERVICES -------------- + ; StrCmp $LANGUAGE "1041" japanese2 + StrCmp $LANGUAGE "1031" german2 + ;Read a value from an InstallOptions INI file + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE "xampp.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE2 "xampp.ini" "Field 4" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE3 "xampp.ini" "Field 6" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE4 "xampp.ini" "Field 7" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE5 "xampp.ini" "Field 8" "State" + Goto defaultlang2 + german2: + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE "xampp-german.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE2 "xampp-german.ini" "Field 4" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE3 "xampp-german.ini" "Field 6" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE4 "xampp-german.ini" "Field 7" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE5 "xampp-german.ini" "Field 8" "State" + Goto defaultlang2 + ; japanese2: + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE "xampp-japanese.ini" "Field 2" "State" + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE2 "xampp-japanese.ini" "Field 4" "State" + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE3 "xampp-japanese.ini" "Field 6" "State" + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE4 "xampp-japanese.ini" "Field 7" "State" + ; !insertmacro MUI_INSTALLOPTIONS_READ $INI_VALUE5 "xampp-japanese.ini" "Field 8" "State" + defaultlang2: + + + StrCmp $INI_VALUE3 "1" "" noapache2 + WriteRegStr HKLM "Software\xampp" "apacheservice" "1" + WriteRegStr HKLM "Software\xampp" "services" "1" + noapache2: + StrCmp $INI_VALUE4 "1" "" nomysql5 + WriteRegStr HKLM "Software\xampp" "mysqlservice" "1" + WriteRegStr HKLM "Software\xampp" "services" "1" + nomysql5: + StrCmp $INI_VALUE5 "1" "" noftp2 + WriteRegStr HKLM "Software\xampp" "filezillaservice" "1" + WriteRegStr HKLM "Software\xampp" "services" "1" + noftp2: + no_srv: + +StrCmp $INI_VALUE "1" "" nodesktop +WriteRegStr HKLM "Software\xampp" "desktopicon" "1" +nodesktop: +StrCmp $INI_VALUE2 "1" "" noprofiles +WriteRegStr HKLM "Software\xampp" "programfiles" "1" +noprofiles: + +SectionEnd + +; --------------------------------------- + +Function .onInit + !insertmacro MUI_LANGDLL_DISPLAY + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp.ini" + ; !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp-japanese.ini" + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp_home.ini" + ; !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp_home-japanese.ini" + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp-german.ini" + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "xampp_home-german.ini" + + ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $R1 "6.0" detection_VISTA + StrCmp $R1 "6.1" detection_VISTA + Goto no_vista + detection_VISTA: + ReadRegStr $R2 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" EnableLUA + ReadRegStr $R0 HKCU "Control Panel\International" Locale + StrCmp $R0 "00000407" detection_de + GOTO no_de + detection_de: + StrCmp $R2 "1" IS_UACDE + MessageBox MB_OK "Die Benutzerkontensteuerung unter Windows (UAC) ist auf Ihrem System deaktiviert (empfohlen). Bitte beachten Sie, das eine nachtrgliche Aktivierung des Benutzerkontenschutz die Funktionalitt der XAMPP-Komponenten beeintrchtigen kann." + GOTO ISNO_UACDE + IS_UACDE: + MessageBox MB_OK "Warnung! Aufgrund der aktivierten Windows Benutzerkontensteuerung (UAC) auf Ihrem System sind XAMPP-Komponenten und Funktionen ggf. nur eingeschrnkt einsetzbar. Vermeiden Sie die Installation von XAMPP unter $PROGRAMFILES oder deaktivieren Sie den Benutzerkontensteuerung ber msconfig nach diesem Setup." + ISNO_UACDE: + GOTO no_vista + no_de: + StrCmp $R2 "1" IS_UACE + MessageBox MB_OK "The User Account Control (UAC) is deactivated on your system (recommended). Please note: A later activation of UAC can restrict the functionality of XAMPP." + GOTO no_vista + IS_UACE: + MessageBox MB_OK "Important! Because an activated User Account Control (UAC) on your sytem some functions of XAMPP are possibly restricted. With UAC please avoid to install XAMPP to $PROGRAMFILES (missing write permisssions). Or deactivate UAC with msconfig after this setup." + no_vista: +FunctionEnd + + +Function CustomPageC +ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $R1 "" 0 NTsrv + ; StrCmp $LANGUAGE "1041" japanesehome + StrCmp $LANGUAGE "1031" germanhome + !insertmacro MUI_HEADER_TEXT "XAMPP Options" "Install options on Windows Home systems." + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "xampp_home.ini" + Goto no_srv + germanhome: + !insertmacro MUI_HEADER_TEXT "XAMPP Optionen" "Konfiguration fr Windows Home Systeme." + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "xampp_home-german.ini" + Goto no_srv + ; japanesehome: + ; !insertmacro MUI_HEADER_TEXT "XAMPP Options" "Windows̃VXẽIvVCXg[܂B" + ; !insertmacro MUI_INSTALLOPTIONS_DISPLAY "xampp_home-japanese.ini" + ; Goto no_srv +NTsrv: + ; StrCmp $LANGUAGE "1041" japanese + StrCmp $LANGUAGE "1031" german + !insertmacro MUI_HEADER_TEXT "XAMPP Options" "Install options on NT/2000/XP Professional systems." + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "xampp.ini" + Goto no_srv + german: + !insertmacro MUI_HEADER_TEXT "XAMPP Optionen" "Konfiguration fr NT/2000/XP Professional Systeme." + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "xampp-german.ini" + Goto no_srv + ; japanese: + ; !insertmacro MUI_HEADER_TEXT "XAMPP Options" "Windows NT/2000/XP/2003ɃVXeIvVCXg[܂" + ; !insertmacro MUI_INSTALLOPTIONS_DISPLAY "xampp-japanese.ini" +no_srv: + +FunctionEnd + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SEC01} "SERVICES:" +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +Function .onInstSuccess + +; SERVICE INSTALLATION +ReadRegStr $4 HKLM "Software\xampp" "lang" +ReadRegStr $0 HKLM "Software\xampp" "services" +StrCmp $0 "0" no_srv +ExecWait 'cmd /C cd "$INSTDIR\install" & portcheck.bat' $7 +ReadRegStr $1 HKLM "Software\xampp" "apacheservice" +StrCmp $1 "0" no_httpd + ReadINIStr $R0 "$INSTDIR\install\portcheck.ini" "Ports" "Port80" + StrCmp $R0 "BLOCKED" Port80Abort + ReadINIStr $R1 "$INSTDIR\install\portcheck.ini" "Ports" "Port443" + StrCmp $R1 "BLOCKED" Port80Abort + ExecWait 'cmd /C cd "$INSTDIR\apache\bin" & httpd.exe -k install & net start Apache2.2' $9 + Goto no_httpd + Port80Abort: + StrCmp $4 "1031" german + ; StrCmp $4 "1041" japan + StrCpy $INST_MESS1 "Ports 80 or 443 (SSL) already in use! Installing Apache2.2 service failed!" + Goto mess1 + ; japan: + ; StrCpy $INST_MESS1 "|[g 80 ܂ 443 (SSL) ͂łɗpĂ܂BApache2.2T[rXƂăCXg[̂Ɏs܂B" + ; Goto mess1 + german: + StrCpy $INST_MESS1 "Ports 80 oder 443 (SSL) bereits in Nutzung! Apache2.2-Dienst konnte nicht eingerichtet werden." + mess1: + WriteRegStr HKLM "Software\xampp" "apacheservice" "0" + MessageBox MB_OK "$INST_MESS1" + +no_httpd: +ReadRegStr $2 HKLM "Software\xampp" "mysqlservice" +StrCmp $2 "0" no_mysql + ReadINIStr $R0 "$INSTDIR\install\portcheck.ini" "Ports" "Port3306" + StrCmp $R0 "BLOCKED" Port3306Abort + ExecWait 'cmd /C cd "$INSTDIR\mysql\bin" & mysqld.exe --install mysql --defaults-file="$INSTDIR\mysql\bin\my.ini" & net start mysql' $9 + Goto no_mysql + Port3306Abort: + StrCmp $4 "1031" german1 + ;StrCmp $4 "1041" japan1 + StrCpy $INST_MESS2 "Port 3306 already in use! Installing MySQL service failed!" + Goto mess2 + ; japan1: + ; StrCpy $INST_MESS2 "|[g3306 ͂łɎgpĂ܂BMySQLT[rXƂăCXg[̂Ɏs܂B" + ; Goto mess2 + german1: + StrCpy $INST_MESS2 "Port 3306 bereits in Nutzung! MySQL-Dienst kann nicht eingerichtet werden." + mess2: + WriteRegStr HKLM "Software\xampp" "mysqlservice" "0" + MessageBox MB_OK "$INST_MESS2" + +no_mysql: +ReadRegStr $3 HKLM "Software\xampp" "filezillaservice" +StrCmp $3 "0" no_ftp + ReadINIStr $R0 "$INSTDIR\install\portcheck.ini" "Ports" "Port21" + StrCmp $R0 "BLOCKED" Port21Abort + ExecWait '"$INSTDIR\FileZillaFTP\FileZillaServer.exe" /install' $9 + ExecWait '"$INSTDIR\FileZillaFTP\FileZillaServer.exe" /start' $9 + Goto no_ftp + Port21Abort: + StrCmp $4 "1031" german2 + ; StrCmp $4 "1041" japan2 + StrCpy $INST_MESS3 "Port 21 already in use! Installing FileZilla FTPD service failed!" + Goto mess3 + ; japan2: + ; StrCpy $INST_MESS3 "|[g 21 ͂łɎgpĂ܂BFileZilla FTPD T[rXƂăCXg[邱ƂɎs܂B" + ; Goto mess3 + german2: + StrCpy $INST_MESS3 "Port 21 bereits in Nutzung! FileZilla-Dienst kann nicht eingerichtet werden!" + mess3: + WriteRegStr HKLM "Software\xampp" "filezillaservice" "0" + MessageBox MB_OK "$INST_MESS3" + +no_ftp: + StrCmp $4 "1031" german3 + ; StrCmp $4 "1041" japan3 + StrCpy $INST_MESS4 "Service installation finished! Hint: Use also the XAMPP Control Panel to manage services." + Goto mess4 + ; japan3: + ; StrCpy $INST_MESS4 "T[rXƂăCXg[ɐ܂BXamppRg[plmFAT[rXǗĂB" + ; Goto mess4 + german3: + StrCpy $INST_MESS4 "Dienste-Installation abgeschlossen! Tipp: Dienste knnen Sie auch mit XAMPP Control Panel verwalten." + mess4: + MessageBox MB_OK "$INST_MESS4" + +no_srv: +; DESKTOP & START MENU SECTION +ReadRegStr $0 HKLM "Software\xampp" "desktopicon" +StrCmp $0 "0" no_icon +CreateShortCut "$DESKTOP\XAMPP Control Panel.lnk" "$INSTDIR\xampp-control.exe" "" + +no_icon: +ReadRegStr $1 HKLM "Software\xampp" "programfiles" +StrCmp $1 "0" no_pfiles + CreateDirectory "$SMPROGRAMS\Apache Friends\XAMPP" + CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP" "" "" + CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\XAMPP Control Panel.lnk" "$INSTDIR\xampp-control.exe" "" "$INSTDIR\install\xampp.ico" + CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\XAMPP htdocs folder.lnk" "$INSTDIR\htdocs" "" "$INSTDIR\install\folder.ico" + ;CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\Port check.lnk" "$INSTDIR\xampp-portcheck.exe" "" "$INSTDIR\install\xamppcontrol.ico" + ;CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\PHP switch.lnk" "$INSTDIR\php-switch.bat" "" "$INSTDIR\install\php.ico" + CreateShortCut "$SMPROGRAMS\Apache Friends\XAMPP\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\install\xampp-icon-uninstall.ico" + +no_pfiles: +; StrCmp $4 "1041" japanese +; Delete "$INSTDIR\xampp-control-jp.exe" +; Rename "$INSTDIR\xampp-control-default.exe" "$INSTDIR\xampp-control.exe" +; Goto xamppcontrol_out +; japanese: +; Delete "$INSTDIR\xampp-control-default.exe" +; Rename "$INSTDIR\xampp-control-jp.exe" "$INSTDIR\xampp-control.exe" +xamppcontrol_out: + +StrCmp $4 "1031" german4 +; StrCmp $4 "1041" japan4 +StrCpy $INST_MESS "Congratulations! The installation was successful! Start the XAMPP Control Panel now?" +GOTO Xcontrol +german4: +StrCpy $INST_MESS "Herzlichen Glckwunsch! Die Installation war erfolgreich! Das XAMPP Control Panel jetzt starten?" +GOTO Xcontrol +; japan4: +; StrCpy $INST_MESS "߂łƂ܂BCXg[ɐ܂BXamppRg[plN܂H" +Xcontrol: +MessageBox MB_YESNO "$INST_MESS" IDNO NoXcontrol + Exec '"$INSTDIR\xampp-control.exe"' +NoXcontrol: +FunctionEnd + + +Section "Uninstall" +Exec '"$INSTDIR\apache\bin\pv.exe" -f -k xampp-control.exe' + +ReadRegStr $9 HKLM "Software\xampp" "lang" +; StrCmp $9 "1041" japanese0 +StrCmp $9 "1031" german0 +StrCpy $INST_MESS "Really uninstall XAMPP with all services?" +GOTO mess_box +german0: +StrCpy $INST_MESS "XAMPP mit allen Server-Diensten wirklich deinstallieren?" +GOTO mess_box +; japanese0: +; StrCpy $INST_MESS "{XamppT[rX܂߂ăACXg[܂H" +mess_box: +MessageBox MB_YESNO|MB_ICONQUESTION "$INST_MESS" IDNO ExitDel + +ReadRegStr $5 HKLM "Software\xampp" "services" +StrCmp $5 "0" srv_Abort +ReadRegStr $2 HKLM "Software\xampp" "apacheservice" +StrCmp $2 "0" no_httpd +ExecWait 'cmd /C net stop Apache2.2 & "$INSTDIR\apache\bin\httpd.exe" -k uninstall' $9 +no_httpd: +ReadRegStr $3 HKLM "Software\xampp" "mysqlservice" +StrCmp $3 "0" no_mysql +ExecWait 'cmd /C net stop mysql & "$INSTDIR\mysql\bin\mysqld.exe" --remove mysql' $9 +no_mysql: +ReadRegStr $4 HKLM "Software\xampp" "filezillaservice" +StrCmp $4 "0" no_ftpd +; Starte: "F:\temp\program files\xampp\FileZillaFTP\FileZillaServer.exe" /stop +; Starte: "F:\temp\program files\xampp\FileZillaFTP\FileZillaServer.exe" /uninstall +; net stop "FileZilla Server FTP server" +ExecWait '"$INSTDIR\FileZillaFTP\FileZillaServer.exe" /stop' $8 +ExecWait '"$INSTDIR\FileZillaFTP\FileZillaServer.exe" /uninstall' $8 +no_ftpd: + +ReadRegStr $6 HKLM "Software\xampp" "tomcatservice" +StrCmp $6 "0" NoJavaAddon +ReadRegStr $7 HKLM "SYSTEM\CurrentControlSet\Services\Tomcat7" "ImagePath" +StrCmp $7 "$INSTDIR\tomcat\bin\tomcat6.exe //RS//Tomcat7" Tomcat5Uninstall +Goto Tomcat5Abort +Tomcat5Uninstall: +MessageBox MB_OK "Service detected! Uninstall Tomcat 7 as service!" +ExecWait 'cmd /C net stop Tomcat7 & cd "$INSTDIR\tomcat\bin" & service.bat remove Tomcat7' $9 +Tomcat5Abort: +NoJavaAddon: + +srv_Abort: + +ReadRegStr $0 HKLM "Software\xampp" "desktopicon" +StrCmp $0 "0" no_icon +Delete "$DESKTOP\XAMPP Control Panel.lnk" +no_icon: +ReadRegStr $8 HKLM "Software\xampp" "programfiles" +StrCmp $8 "0" no_pfiles + Delete "$SMPROGRAMS\Apache Friends\xampp\*.*" + RMDir "$SMPROGRAMS\Apache Friends\xampp" + RMDir "$SMPROGRAMS\Apache Friends" +no_pfiles: + + + +RMDir /r "$INSTDIR\anonymous" +RMDir /r "$INSTDIR\apache" +RMDir /r "$INSTDIR\cgi-bin" +RMDir /r "$INSTDIR\FileZillaFTP" +RMDir /r "$INSTDIR\install" +RMDir /r "$INSTDIR\licenses" +RMDir /r "$INSTDIR\MercuryMail" +RMDir /r "$INSTDIR\perl" +RMDir /r "$INSTDIR\php" +RMDir /r "$INSTDIR\phpMyAdmin" +RMDir /r "$INSTDIR\python" +RMDir /r "$INSTDIR\security" +RMDir /r "$INSTDIR\sendmail" +RMDir /r "$INSTDIR\tmp" +RMDir /r "$INSTDIR\tomcat" +RMDir /r "$INSTDIR\webalizer" +RMDir /r "$INSTDIR\webdav" +RMDir /r "$INSTDIR\nsis" +RMDir /r "$INSTDIR\contrib" +RMDir /r "$INSTDIR\src" + +Delete "$INSTDIR\apache_start.bat" +Delete "$INSTDIR\apache_stop.bat" +Delete "$INSTDIR\filezilla_setup.bat" +Delete "$INSTDIR\filezilla_start.bat" +Delete "$INSTDIR\filezilla_stop.bat" +Delete "$INSTDIR\mercury_start.bat" +Delete "$INSTDIR\mercury_stop.bat" +Delete "$INSTDIR\mysql_start.bat" +Delete "$INSTDIR\mysql_stop.bat" +Delete "$INSTDIR\php-switch.bat" +Delete "$INSTDIR\readme_de.txt" +Delete "$INSTDIR\readme_en.txt" +Delete "$INSTDIR\service.exe" +Delete "$INSTDIR\setup_xampp.bat" +Delete "$INSTDIR\xampp_restart.exe" +Delete "$INSTDIR\xampp_start.exe" +Delete "$INSTDIR\xampp_stop.exe" +Delete "$INSTDIR\xampp-changes.txt" +Delete "$INSTDIR\xampp-portcheck.exe" +Delete "$INSTDIR\xampp-control.exe" +Delete "$INSTDIR\Uninstall.exe" +Delete "$INSTDIR\javapath.ini" +Delete "$INSTDIR\readme-addon-perl.txt" +Delete "$INSTDIR\readme-addon-tomcat.txt" +Delete "$INSTDIR\tomcat_start.bat" +Delete "$INSTDIR\tomcat_stop.bat" +Delete "$INSTDIR\passwords.txt" +Delete "$INSTDIR\xampp_cli.exe" +Delete "$INSTDIR\xampp_chkdll.exe" +Delete "$INSTDIR\xampp_service_mercury.exe" +Delete "$INSTDIR\catalina_start.bat" +Delete "$INSTDIR\catalina_stop.bat" +Delete "$INSTDIR\xampp-control-3-beta.exe" +;Delete "$INSTDIR\msvcr71.dll" + +DeleteRegKey HKLM "Software\xampp" +DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\xampp" + +; StrCmp $LANGUAGE "1041" japanese1 +StrCmp $LANGUAGE "1031" german1 +StrCpy $INST_MESS1 "Remove all MySQL data bases from $INSTDIR\mysql\data?" +GOTO messa1 +german1: +StrCpy $INST_MESS1 "Alle MySQL Datenbanken in $INSTDIR\mysql\data lschen?" +GOTO messa1 +; japanese1: +; StrCpy $INST_MESS1 "MySQLf[^x[X $INSTDIR\mysql\data 폜܂H" +messa1: +MessageBox MB_YESNO|MB_ICONQUESTION "$INST_MESS1" IDYES NoMysql +Delete "$INSTDIR\mysql\*.*" +RMDir /r "$INSTDIR\mysql\bin" +RMDir /r "$INSTDIR\mysql\scripts" +RMDir /r "$INSTDIR\mysql\share" +StrCpy $DB_DEL "0" +Goto DeleteHtdocs +NoMysql: +RMDir /r "$INSTDIR\mysql" +DeleteHtdocs: + +; StrCmp $LANGUAGE "1041" japanese2 +StrCmp $LANGUAGE "1031" german2 +StrCpy $INST_MESS2 "Remove the $INSTDIR\htdocs folder too?" +StrCpy $MESS_INSTDIR1 "Shall the installer try to remove $INSTDIR?" +StrCpy $MESS_INSTDIR2 "Note: $INSTDIR could not be removed!" +GOTO messa2 +german2: +StrCpy $INST_MESS2 "Auch das Verzeichnis $INSTDIR\htdocs lschen?" +StrCpy $MESS_INSTDIR1 "Soll der Installer (versuchen) das Verzeichnis $INSTDIR (zu) lschen?" +StrCpy $MESS_INSTDIR2 "Achtung: Knnte $INSTDIR nicht lschen!" +GOTO messa2 +; japanese2: +; StrCpy $INST_MESS2 "$INSTDIR\htdocs ̃tH_[폜܂H" +; StrCpy $MESS_INSTDIR1 "Shall the installer try to remove $INSTDIR?" +; StrCpy $MESS_INSTDIR2 "Note: $INSTDIR could not be removed!" +messa2: +MessageBox MB_YESNO|MB_ICONQUESTION "$INST_MESS2" IDYES noHtdocs +Goto NoDelete +noHtdocs: +RMDir /r "$INSTDIR\htdocs" +GOTO NoDocs +NoDelete: +GOTO ExitDel +NoDocs: +StrCmp $DB_DEL "0" NoXaDir + +MessageBox MB_YESNO|MB_ICONQUESTION "$MESS_INSTDIR1" IDYES noIDIR +Goto yesIDIR +noIDIR: +RMDir "$INSTDIR" +IfFileExists "$INSTDIR\*.*" ErrorMsg +Goto yesIDIR +ErrorMsg: +MessageBox MB_OK "$MESS_INSTDIR2" ; skipped if file doesn't exist +yesIDIR: + +NoXaDir: +ExitDel: +SectionEnd + +;-------------------------------- +;Uninstaller Functions +Function un.onInit + !insertmacro MUI_UNGETLANGUAGE +FunctionEnd diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home-german.ini b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home-german.ini new file mode 100755 index 0000000000..85038a1cf9 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home-german.ini @@ -0,0 +1,45 @@ +[Settings] +NumFields=5 + +[Field 1] +Type=label +Text=XAMPP DESKTOP +Left=40 +Right=-1 +Top=0 +Bottom=10 + +[Field 2] +Type=checkbox +Text=XAMPP als Desktop Icon +Left=40 +Right=-1 +Top=10 +Bottom=25 +State=1 + +[Field 3] +Type=label +Text=XAMPP START MEN +Left=40 +Right=-1 +Top=30 +Bottom=40 + +[Field 4] +Type=checkbox +Text=Apache Friends XAMPP Eintrag unter Start/Programme +Left=40 +Right=-1 +Top=40 +Bottom=55 +State=1 + +[Field 5] +Type=link +Left=40 +Right=-1 +Top=115 +Bottom=125 +State=http://www.apachefriends.org/en/faq-xampp-windows.html +Text=Bitte besuchen Sie auch die XAMPP Windows FAQ Website diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home-japanese.ini b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home-japanese.ini new file mode 100755 index 0000000000..d35baf43b8 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home-japanese.ini @@ -0,0 +1,45 @@ +[Settings] +NumFields=5 + +[Field 1] +Type=label +Text=XAMPP fXNgbv +Left=40 +Right=-1 +Top=0 +Bottom=10 + +[Field 2] +Type=checkbox +Text=fXNgbvXamppACR쐬 +Left=40 +Right=-1 +Top=10 +Bottom=25 +State=1 + +[Field 3] +Type=label +Text=XAMPP X^[gj[ +Left=40 +Right=-1 +Top=30 +Bottom=40 + +[Field 4] +Type=checkbox +Text=X^[gj[Apache Friends XAMPP tH_[쐬 +Left=40 +Right=-1 +Top=40 +Bottom=55 +State=1 + +[Field 5] +Type=link +Left=40 +Right=-1 +Top=115 +Bottom=125 +State=http://www.apachefriends.org/en/faq-xampp-windows.html +Text=XAMPP WindowsłFAQ y[W diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home.ini b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home.ini new file mode 100755 index 0000000000..014c213892 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/scripts/xampp_home.ini @@ -0,0 +1,45 @@ +[Settings] +NumFields=5 + +[Field 1] +Type=label +Text=XAMPP DESKTOP +Left=40 +Right=-1 +Top=0 +Bottom=10 + +[Field 2] +Type=checkbox +Text=Create a XAMPP desktop icon +Left=40 +Right=-1 +Top=10 +Bottom=25 +State=1 + +[Field 3] +Type=label +Text=XAMPP START MENU +Left=40 +Right=-1 +Top=30 +Bottom=40 + +[Field 4] +Type=checkbox +Text=Create an Apache Friends XAMPP folder in the start menu +Left=40 +Right=-1 +Top=40 +Bottom=55 +State=1 + +[Field 5] +Type=link +Left=40 +Right=-1 +Top=115 +Bottom=125 +State=http://www.apachefriends.org/en/faq-xampp-windows.html +Text=See also the XAMPP for Windows FAQ Page diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/1511.bmp b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/1511.bmp new file mode 100755 index 0000000000..4e985c3733 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/1511.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/1511.gif.png b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/1511.gif.png new file mode 100755 index 0000000000..86eb10b623 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/1511.gif.png differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/Modern-uninstall-nblue.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/Modern-uninstall-nblue.ico new file mode 100755 index 0000000000..65eac2ea28 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/Modern-uninstall-nblue.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/folder.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/folder.ico new file mode 100755 index 0000000000..57d29a7585 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/folder.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/php.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/php.ico new file mode 100755 index 0000000000..2b65f3fa0e Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/php.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/php.png b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/php.png new file mode 100755 index 0000000000..3f76feb102 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/php.png differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/portcheck.bat b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/portcheck.bat new file mode 100755 index 0000000000..8d032c5ee1 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/portcheck.bat @@ -0,0 +1,5 @@ +@echo off +echo "Checking necessary ports! Please wait ..." +set PHP_BIN=..\php\php.exe +set CONFIG_PHP=portcheck.php -80,443,3306,21,14147,8080 +%PHP_BIN% -n -d output_buffering=0 %CONFIG_PHP% diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/tomcat.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/tomcat.ico new file mode 100755 index 0000000000..e1732e0ce9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/tomcat.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/tomcatstop.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/tomcatstop.ico new file mode 100755 index 0000000000..ef98bd5d94 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/tomcatstop.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xampp-icon-uninstall.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xampp-icon-uninstall.ico new file mode 100755 index 0000000000..15043400a9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xampp-icon-uninstall.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xampp.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xampp.ico new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xampp.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xamppcontrol.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xamppcontrol.ico new file mode 100755 index 0000000000..b4f2ebf7c4 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xamppcontrol.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xamppstop.ico b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xamppstop.ico new file mode 100755 index 0000000000..add7673003 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-nsi-installer/xa-icons/xamppstop.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/Makefile b/trunk/build/windows/xampp/src/xampp-start-stop/Makefile new file mode 100755 index 0000000000..947f399ea6 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/Makefile @@ -0,0 +1,28 @@ +all: checkdll.exe xampp_start.exe xampp_stop.exe xamppcli.exe + +clean: + rm checkdll.exe xampp_start.exe xampp_stop.exe wrapper.exe xamppcli.exe + rm *.obj + +checkdll.exe: checkdll.c + cl /nologo checkdll.c + cp checkdll.exe /c/XAMPP/bin + +xampp_util.obj: xampp_util.c + cl /nologo /c xampp_util.c + +xampp.res: xampp.rc + rc xampp.rc + +xampp_start.exe: xampp_start.c xampp_util.obj xampp_util.h xampp.res + cl /nologo xampp_start.c xampp_util.obj xampp.res + cp xampp_start.exe /c/XAMPP/start.exe + +xampp_stop.exe: xampp_stop.c xampp_util.obj xampp_util.h xampp.res + cl /nologo xampp_stop.c xampp_util.obj xampp.res + cp xampp_stop.exe /c/XAMPP/stop.exe + +xamppcli.exe: xamppcli.c xampp_util.obj xampp_util.h xampp.res + cl /nologo xamppcli.c xampp_util.obj xampp.res + cp xamppcli.exe /c/XAMPP/bin + diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/checkdll.c b/trunk/build/windows/xampp/src/xampp-start-stop/checkdll.c new file mode 100755 index 0000000000..166d5ee49f --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/checkdll.c @@ -0,0 +1,27 @@ +// Copyright (C) 2007-2010 Kai Seidler, oswald@apachefriends.org, GPL-licensed + +#include +#include + +int main(int argc, char **argv) +{ + if(argc!=2) + { + printf("Usage: %s \n",argv[0]); + exit(2); + } + + if(LoadLibrary(argv[1])) + { + printf("OK\n"); + exit(0); + } + else + { + printf("NOK\n"); + exit(1); + } + + + +} diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/rc.h b/trunk/build/windows/xampp/src/xampp-start-stop/rc.h new file mode 100755 index 0000000000..6498c64264 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/rc.h @@ -0,0 +1,7 @@ +#ifndef _RC_H_INCLUDED +#define _RC_H_INCLUDED + +#define XAMPP_ICON 300 + + +#endif // _RC_H_INCLUDED diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xampp.ico b/trunk/build/windows/xampp/src/xampp-start-stop/xampp.ico new file mode 100755 index 0000000000..ca0acbbdb9 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-start-stop/xampp.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xampp.rc b/trunk/build/windows/xampp/src/xampp-start-stop/xampp.rc new file mode 100755 index 0000000000..ade0739c20 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/xampp.rc @@ -0,0 +1,42 @@ +#include +#include "rc.h" + +XAMPP_ICON ICON xampp.ico + +#define VER_FILEVERSION 1,0 +#define VER_FILEVERSION_STR "1.0" +#define VER_PRODUCTVERSION 1,0,0,0 +#define VER_PRODUCTVERSION_STR "1.0.0.0" + +// ------------------------------------------------------- + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILEVERSION + PRODUCTVERSION VER_PRODUCTVERSION + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN +BLOCK "StringFileInfo" +BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Apache Friends" + VALUE "FileDescription", "Start and stop XAMPP" + VALUE "LegalCopyright", "Copyright (C) 2010 Kai Seidler, Apache Friends" + VALUE "ProductName", "XAMPPcli" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END +END +BLOCK "VarFileInfo" +BEGIN + VALUE "Translation", 0x409, 1200 +END +END diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xampp_start.c b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_start.c new file mode 100755 index 0000000000..019132b4e6 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_start.c @@ -0,0 +1,33 @@ +// Copyright (C) 2007-2010 Kai Seidler, oswald@apachefriends.org, GPL-licensed + +#include +#include +#include + +#include "xampp_util.h" + +int main(int argc, char **argv) +{ + char buffer[200]; + char *start[10][10] = { + {"apache\\bin\\httpd.exe", "-f conf\\httpd.conf", NULL}, + {"mysql\\bin\\mysqld.exe", "--defaults-file=mysql\\bin\\my.ini", "--standalone", NULL}, + {NULL} + }; + + printf("\nXAMPP now starts as a console application.\n\n"); + + printf("Instead of pressing Control-C in this console window, please use xampp_stop.exe\n"); + printf("to stop XAMPP, because it lets XAMPP end any current transactions and cleanup\n"); + printf("gracefully.\n\n"); + + + xampp_cdx(); + + xampp_call(start); + + Sleep(10000); + + + return(0); +} diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xampp_stop.c b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_stop.c new file mode 100755 index 0000000000..8e2e8ca823 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_stop.c @@ -0,0 +1,74 @@ +// Copyright (C) 2007-2010 Kai Seidler, oswald@apachefriends.org, GPL-licensed + +#include +#include +#include + +#include "xampp_util.h" + +int main(int argc, char **argv) +{ + long pid; + HANDLE shutdownEvent; + char shutdownEventName[32]; + FILE *fp; + char *pidfile; + + printf("Stopping XAMPP...\n\n"); + + xampp_cdx(); + + pidfile="apache\\logs\\httpd.pid"; + fp=fopen(pidfile,"r"); + if(!fp) + { + printf("Can't find %s.\n", pidfile); + Sleep(10000); + } + else + { + fscanf(fp,"%d", &pid); + fclose(fp); + + sprintf_s(shutdownEventName, sizeof(shutdownEventName), "ap%d_shutdown", pid); + shutdownEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, shutdownEventName); + if (shutdownEvent != NULL) + { + SetEvent(shutdownEvent); + } + else + { + printf("Can't find Apache process #%d.\n", pid); + Sleep(10000); + } + } + + pidfile="mysql\\data\\mysql.pid"; + fp=fopen(pidfile,"r"); + if(!fp) + { + printf("Can't find %s.\n", pidfile); + Sleep(10000); + return(1); + } + else + { + fscanf(fp,"%d", &pid); + fclose(fp); + + sprintf_s(shutdownEventName, sizeof(shutdownEventName), "MySQLShutdown%d", pid); + shutdownEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, shutdownEventName); + if (shutdownEvent != NULL) + { + SetEvent(shutdownEvent); + } + else + { + printf("Can't find MySQL process #%d.\n", pid); + Sleep(10000); + return(1); + } + } + Sleep(10000); + return(0); +} diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xampp_util.c b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_util.c new file mode 100755 index 0000000000..ab3764df1f --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_util.c @@ -0,0 +1,78 @@ +// Copyright (C) 2007-2010 Kai Seidler, oswald@apachefriends.org, GPL-licensed + +#include +#include +#include +#include + +#include "xampp_util.h" + +void xampp_call(char *start[10][10]) +{ + int s; + int i; + char buffer[200]; + + i=0; + s=0; + while(1) + { + if(start[i][0]==NULL) + break; + sprintf(buffer,"%s",start[i][0]); + s=_spawnvp(P_NOWAIT,buffer, start[i]); + if(s==-1) + { + printf("Error while calling %s...\n",buffer); + } + i++; + } + + return; +} + +void xampp_stop(char *pidfile,char *eventformat) +{ + HANDLE shutdownEvent; + char shutdownEventName[32]; + FILE *fp; + long pid; + + fp=fopen(pidfile,"r"); + if(!fp) + { + printf("Can't find %s.\n", pidfile); + } + else + { + fscanf(fp,"%d", &pid); + fclose(fp); + + sprintf_s(shutdownEventName, sizeof(shutdownEventName), eventformat, pid); + shutdownEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, shutdownEventName); + if (shutdownEvent != NULL) + { + SetEvent(shutdownEvent); + } + else + { + printf("Can't find process #%d.\n", pid); + } + } + +} + +void xampp_cdx() +{ + char path[1000]; + char *ptr; + + //path[ sizeof(path) -1] = 0; + + GetModuleFileName( NULL, path, sizeof( path ) -1 ); + ptr=strrchr(path,'\\'); + *ptr='\0'; + + chdir(path); + //printf("chdir(%s)\n", path); +} diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xampp_util.h b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_util.h new file mode 100755 index 0000000000..92ac43895f --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/xampp_util.h @@ -0,0 +1,4 @@ +// Copyright (C) 2007-2010 Kai Seidler, oswald@apachefriends.org, GPL-licensed + +void xampp_call(char *start[10][10]); +void xampp_cdx(); diff --git a/trunk/build/windows/xampp/src/xampp-start-stop/xamppcli.c b/trunk/build/windows/xampp/src/xampp-start-stop/xamppcli.c new file mode 100755 index 0000000000..9cc0c5b22e --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-start-stop/xamppcli.c @@ -0,0 +1,107 @@ +// Copyright (C) 2007-2010 Kai Seidler, oswald@apachefriends.org, GPL-licensed + +#include +#include +#include + +#include "xampp_util.h" + +int main(int argc, char **argv) +{ + long pid; + HANDLE shutdownEvent; + char shutdownEventName[32]; + FILE *fp; + char *pidfile; + char *startapache[10][10] = { {"httpd.exe", "-DPHP5", NULL}, {NULL} }; + char *startmysql[10][10] = { {"mysqld.exe", "--standalone", NULL}, {NULL} }; + + if(argc!=2) + { + printf("Usage: %s \n",argv[0]); + return 1; + } + + xampp_cdx(); + chdir(".."); + + if(!strcmp(argv[1],"stopapache")) + { + xampp_stop("logs\\httpd.pid","ap%d_shutdown"); + } + else if(!strcmp(argv[1],"startapache")) + { + xampp_call(startapache); + } + else if(!strcmp(argv[1],"startmysql")) + { + xampp_call(startmysql); + } + else if(!strcmp(argv[1],"stopmysql")) + { + xampp_stop("var\\mysql\\mysql.pid","MySQLShutdown%d"); + } + else + { + printf("Unknown command %s\n",argv[1]); + return 2; + } + return 0; + + printf("Stopping XAMPP...\n"); + + Sleep(1000); + + xampp_cdx(); + + pidfile="logs\\httpd.pid"; + fp=fopen(pidfile,"r"); + if(!fp) + { + printf("Can't find %s.\n", pidfile); + } + else + { + fscanf(fp,"%d", &pid); + fclose(fp); + + sprintf_s(shutdownEventName, sizeof(shutdownEventName), "ap%d_shutdown", pid); + shutdownEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, shutdownEventName); + if (shutdownEvent != NULL) + { + SetEvent(shutdownEvent); + } + else + { + printf("Can't find Apache process #%d.\n", pid); + return(1); + } + } + + pidfile="var\\mysql\\mysql.pid"; + fp=fopen(pidfile,"r"); + if(!fp) + { + printf("Can't find %s.\n", pidfile); + return(1); + } + else + { + fscanf(fp,"%d", &pid); + fclose(fp); + + pidfile="var\\mysql\\mysql.pid"; + sprintf_s(shutdownEventName, sizeof(shutdownEventName), "MySQLShutdown%d", pid); + shutdownEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, shutdownEventName); + if (shutdownEvent != NULL) + { + SetEvent(shutdownEvent); + } + else + { + printf("Can't find MySQL process #%d.\n", pid); + return(1); + } + } + return(0); +} diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/make-usb-xampp.bat b/trunk/build/windows/xampp/src/xampp-usb-lite/make-usb-xampp.bat new file mode 100755 index 0000000000..967b181ee9 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/make-usb-xampp.bat @@ -0,0 +1,44 @@ +@ECHO OFF + +rmdir /S/Q MercuryMail +rmdir /S/Q tomcat +rmdir /S/Q FileZillaFTP +rmdir /S/Q anonymous +rmdir /S/Q webalizer +rmdir /S/Q perl\site +rmdir /S/Q perl\lib +rmdir /S/Q perl\bin\.cpan +rmdir /S/Q perl\bin\.cpanplus +rmdir /S/Q perl\bin\.ppm +rmdir /S/Q mysql\mysql-test +rmdir /S/Q php\PEAR +del /F/Q perl\bin\libeay32.dll +del /F/Q perl\bin\ssleay32.dll +del /F/Q perl\bin\libmysql.dll +del /F/Q catalina_start.bat +del /F/Q catalina_stop.bat +del /F/Q filezilla_setup.bat +del /F/Q filezilla_start.bat +del /F/Q filezilla_stop.bat +del /F/Q mercury_start.bat +del /F/Q mercury_stop.bat +del /F/Q service.exe +del /F/Q apache\apache_installservice.bat +del /F/Q apache\apache_uninstallservice.bat +del /F/Q mysql\mysql_installservice.bat +del /F/Q mysql\mysql_uninstallservice.bat +del /F/Q htdocs\xampp\navitools.inc +del /F/Q htdocs\xampp\naviperl.inc +del /F/Q htdocs\xampp\navijava.inc +del /F/Q htdocs\xampp\.modell +del /F/Q apache\conf\extra\httpd-perl.conf +del /F/Q setup_xampp.bat +del /F/Q xampp-control.exe +copy src\xampp-usb-lite\setup_xampp.bat . +copy src\xampp-usb-lite\usb-control-panel\Release\xampp-control.exe . +mv htdocs\xampp\navitools-usb.inc htdocs\xampp\navitools.inc +mv htdocs\xampp\naviperl-usb.inc htdocs\xampp\naviperl.inc +mv htdocs\xampp\navijava-usb.inc htdocs\xampp\navijava.inc +mv apache\conf\extra\httpd-perl-usb.conf apache\conf\extra\httpd-perl.conf +mv htdocs\xampp\.modell-usb htdocs\xampp\.modell + diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/setup_xampp.bat b/trunk/build/windows/xampp/src/xampp-usb-lite/setup_xampp.bat new file mode 100755 index 0000000000..b7cf9bf509 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/setup_xampp.bat @@ -0,0 +1,22 @@ +@ECHO OFF + +if "%1" == "sfx" ( + cd xampp +) +if exist php\php.exe GOTO Normal +if not exist php\php.exe GOTO Abort + +:Abort +echo Sorry ... cannot find php cli! +echo Must abort these process! +pause +GOTO END + +:Normal +set PHP_BIN=php\php.exe +set CONFIG_PHP=install\install.php +%PHP_BIN% -n -d output_buffering=0 -q %CONFIG_PHP% usb +GOTO END + +:END +pause diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/BITMAP2.BMP b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/BITMAP2.BMP new file mode 100755 index 0000000000..1ac96e5a14 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/BITMAP2.BMP differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.obj b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.obj new file mode 100755 index 0000000000..97c72368e6 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.obj differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.res b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.res new file mode 100755 index 0000000000..9e95d88df5 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.res differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.sbr b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.sbr new file mode 100755 index 0000000000..95d6ee12d3 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/XAMPP.sbr differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/vc60.idb b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/vc60.idb new file mode 100755 index 0000000000..325a9e7451 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/vc60.idb differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/vc60.pdb b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/vc60.pdb new file mode 100755 index 0000000000..44c7b37a69 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/vc60.pdb differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.ilk b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.ilk new file mode 100755 index 0000000000..3201db6fcb Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.ilk differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.pch b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.pch new file mode 100755 index 0000000000..511f84ddfd Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.pch differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.pdb b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.pdb new file mode 100755 index 0000000000..a289768ec0 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/Debug/xampp_control.pdb differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ICON1.ICO b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ICON1.ICO new file mode 100755 index 0000000000..8ba57228fa Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ICON1.ICO differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/StdAfx.h b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/StdAfx.h new file mode 100755 index 0000000000..f84a159a7e --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/StdAfx.h @@ -0,0 +1,29 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__202939CE_5B83_453D_A1C9_63457E43CBB0__INCLUDED_) +#define AFX_STDAFX_H__202939CE_5B83_453D_A1C9_63457E43CBB0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +//#define STRICT +#define WIN32_LEAN_AND_MEAN +#include + +#ifdef UNICODE +#define stristr stristrW +#else +#define stristr stristrA +#endif + +char* __fastcall stristrA(const char* psz1, const char* psz2); +WCHAR* __fastcall stristrW(const WCHAR* pszMain, const WCHAR* pszSub); + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__202939CE_5B83_453D_A1C9_63457E43CBB0__INCLUDED_) diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/TRAY.ICO b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/TRAY.ICO new file mode 100755 index 0000000000..7e41175181 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/TRAY.ICO differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/XAMPP.APS b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/XAMPP.APS new file mode 100755 index 0000000000..b1e86fb96c Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/XAMPP.APS differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/XAMPP.C b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/XAMPP.C new file mode 100755 index 0000000000..a27370f024 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/XAMPP.C @@ -0,0 +1,3479 @@ +// xampp.c - Winmain +// +// Visual Studio 6 version +// XP manifest included in xampp.rc +// +// Copyright NAT Software, 2007. http://www.nat32.com/xampp +// +// Modified by ൺ촴Ƽ޹˾ 2012.02.07 http://www.zentao.net +// +// Bugfixes +// +// 11. February, 2006: After creating a Worker thread, wait for hSem to +// ensure that we don't exit before CreateProcess has +// returned. +// +// All semaphores are created before UpdateStatus is +// called. +// +// 17. May, 2006: MySql args now use the full pathname. +// +// 9. May, 2007 PV.EXE no longer used to check status +// Multiple Apache and MySql processes now supported +// Optional xampp.ini file now processed +// +#include +#include +#include +#include + +#include "xampp.h" +#include "stdafx.h" +#include "stdio.h" + +// Global variables + +struct ProcessInfo { + int pid; + char module[256]; + char path[MAX_PATH]; +}; + +struct ProcessInfo proc_tab[1024]; +int proc_index; + +int xampp_service; +int xampp_app; + +char current_user[256]; + +char win_ver[256]; + +OSVERSIONINFO version; +OSVERSIONINFOEX versionx; + +int platform; +int platforme; +int platform2; +int platformx; +int platformx2; +int platformv; + +int icon_up; +char localhost[20] = "http://localhost"; +int argc; +char *argv[16]; + +char cur_dir[1024]; +char ini_file[1024]; +char install_dir[1024]; + +HINSTANCE hInst; + +HWND hAbout; + +HWND hList; +HWND hWnd; +HWND hHelp; +HFONT hFont; + +SC_HANDLE hSCM; +SC_HANDLE hService; +SERVICE_STATUS ss; + +HANDLE hStopEvent; // SCM sets this +HANDLE hServiceStopEvent; // Used to notify the SCM +HANDLE hWatchdogEvent; + +char saved_value[1024]; + +HWND hServiceDlg; +int service_arg; +int service_type; +int service_changed; +int service_enabled; +int splash; + +int timerID; + +NOTIFYICONDATA iconData; +HICON hIcon; + +int incr; +int dlg_flag; +int hide_flag; +int update_flag; +int system_shutdown; +int xampp_flag; + +unsigned long exit_code; + +struct job_entry job[NJOBS]; + +char module[NJOBS][64]; +char action[NJOBS][8]; +HBRUSH hBrushOn; +HBRUSH hBrushOff; + +char hide[64]; +char apache_port80[64]; +char mysql_port3306[64]; +char apache_port88[64]; +char mysql_port3308[64]; +char apache_start[64]; +char mysql_start[64]; +char apache_stop[64]; +char mysql_stop[64]; +char ftp_port[64]; +char mail_port[64]; +char ftp_start[64]; +char mail_start[64]; +char ftp_stop[64]; +char mail_stop[64]; + +char xampp_setup[64]; +char xampp_dir[512]; + +int WINAPI WinMain(HINSTANCE hExe, HINSTANCE hPrev, LPSTR CmdLine, int CmdShow) +{ + char *pstart; + char *pnext; + int i, result; + WNDCLASSEX wcx; + INITCOMMONCONTROLSEX InitCtrls; + + hInst = hExe; + + SetProcessShutdownParameters(0x3FF, 0); + + InitCtrls.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCtrls.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES; + InitCommonControlsEx(&InitCtrls); + + #define MYON RGB(200,255,200) + #define MYOFF GetSysColor(COLOR_3DFACE) + + hBrushOn = CreateSolidBrush(MYON); + hBrushOff = CreateSolidBrush(MYOFF); + + // Register a WNDCLASS + + wcx.cbSize = sizeof(WNDCLASSEX); + wcx.style = CS_HREDRAW | CS_VREDRAW; + wcx.lpfnWndProc = DefDlgProc; + wcx.cbClsExtra = 0; + wcx.cbWndExtra = DLGWINDOWEXTRA; + wcx.hInstance = hInst; + wcx.hIcon = LoadIcon(hInst, "MyIcon"); + wcx.hCursor = LoadCursor(NULL, IDC_ARROW); + wcx.hbrBackground = CreateSolidBrush(COLOR_WINDOW + 1); + wcx.lpszMenuName = NULL; + wcx.lpszClassName = "XamppClass"; + wcx.hIconSm = LoadIcon(hInst, "TrayIcon"); + RegisterClassEx(&wcx); + + // Get platform + + version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&version); + + if (version.dwMajorVersion == 5) { + platform2 = 1; + if (version.dwMinorVersion >= 1) { + platformx = version.dwMinorVersion; + + // Check for SP2 on XP + + if (platformx == 1) { // XP only, not 2003 + versionx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + GetVersionEx((LPOSVERSIONINFO)&versionx); + if (versionx.wServicePackMajor == 2) + platformx2 = 2; + } + + // Check for SP1 on 2003 + + if (platformx == 2) { // 2003 only + versionx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + GetVersionEx((LPOSVERSIONINFO)&versionx); + if (versionx.wServicePackMajor >= 1) + platformx2 = 2; + } + } + } + + if (version.dwMajorVersion == 6) { + + // Assume that Vista has everything and then some + + platform2 = 1; + platformx = 2; + platformx2 = 2; + platformv = 1; + } + + if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) + platform = 1; + + if (platform == 0 && version.dwMinorVersion == 90) // Windows 98ME + platforme = 1; + + sprintf(win_ver, "Windows %d.%d Build %d Platform %d %s", + version.dwMajorVersion, + version.dwMinorVersion, + platform?version.dwBuildNumber:LOWORD(version.dwBuildNumber), + version.dwPlatformId, + version.szCSDVersion); + + GetCurrentDirectory(1024, cur_dir); + + // Get settings from xampp.ini + + sprintf(ini_file, "%s\\xampp.ini", cur_dir); + + GetPrivateProfileString("START", "hide", "0", hide, 64, ini_file); + GetPrivateProfileString("START", "httpd", "0", apache_start, 64, ini_file); + GetPrivateProfileString("START", "mysql", "0", mysql_start, 64, ini_file); + + GetPrivateProfileString("EXIT", "httpd", "0", apache_stop, 64, ini_file); + GetPrivateProfileString("EXIT", "mysql", "0", mysql_stop, 64, ini_file); + + GetPrivateProfileString("PORTS", "apache", "80", apache_port80, 64, ini_file); + GetPrivateProfileString("PORTS", "mysql", "3306", mysql_port3306, 64, ini_file); + GetPrivateProfileString("PORTS", "apache", "88", apache_port88, 64, ini_file); + GetPrivateProfileString("PORTS", "mysql", "3308", mysql_port3308, 64, ini_file); + + GetPrivateProfileString("PORTS", "ftp", "21", ftp_port, 64, ini_file); + GetPrivateProfileString("PORTS", "mercury","25", mail_port, 64, ini_file); + + GetPrivateProfileString("XAMPP", "setup", "0", xampp_setup, 64, ini_file); + + sprintf(ini_file, "%s\\apache\\bin\\php.ini", cur_dir); + + GetPrivateProfileString("PHP", "extension_dir", "0", xampp_dir, 512, ini_file); + + if (stristr(xampp_dir, cur_dir)) + xampp_flag = 0; + else + xampp_flag = 1; + + // Initialize job names and ports + + strcpy(job[0].name, "httpd80"); + strcpy(job[0].port, apache_port80); + strcpy(job[1].name, "mysql3306"); + strcpy(job[1].port, mysql_port3306); + strcpy(job[2].name, "httpd88"); + strcpy(job[2].port, apache_port88); + strcpy(job[3].name, "mysql3308"); + strcpy(job[3].port, mysql_port3308); + + // Get current User Name + + i = 1024; + GetUserName(current_user, (DWORD *)&i); + + // Generate standard string args + + argc = 1; + argv[0] = "xampp"; + pstart = CmdLine; + + while (strlen(pstart)) { + + argv[argc++] = pstart; + pnext = strchr(pstart, ' '); + + if (pnext == 0) + break; + + *pnext++ = 0; + pstart = pnext; + + if (argc > 16) { + kprintf("Usage: zentaoamp [module]+"); + return 0; + } + while (*pstart && isspace(*pstart)) pstart++; + } + + if (argv[argc-1][0] == 's' && argv[argc-1][1] == 0) { + argc--; + service_arg = 1; + } + + result = start(); + + if (result == OK) { + + // Create the WatchdogEvent + + CloseHandle(hWatchdogEvent); + + hWatchdogEvent = CreateEvent(NULL, TRUE, FALSE, "ZENTAOAMPWATCHDOG"); + + if (hWatchdogEvent == 0) + kprintf("FATAL ERROR: The Watchdog Event could not be created."); + else + work(argc, argv); + } + + if (hServiceStopEvent) { + SetEvent(hServiceStopEvent); // Notify the SCM + Shell_NotifyIcon(NIM_DELETE, &iconData); + } + + return 1; +} + +int start() { + + int result; + + // Is another instance running? + + if (platform == 0) { + + // For 9X platforms, check for the Watchdog event + + xampp_app = 1; + + hWatchdogEvent = OpenEvent(EVENT_ALL_ACCESS, TRUE, "ZENTAOAMPAMPWATCHDOG"); + + if (hWatchdogEvent) { + HWND hTemp; + // A previous instance exists + + hTemp = FindWindow("XAMPPAPP", "zentaoamp"); + if (hTemp != 0) { + ShowWindow(hTemp, SW_SHOWDEFAULT); + SetForegroundWindow(hTemp); // give focus to other window + if (argc == 2 && !strcmp(argv[1], "-t")) // terminate the other one + SendMessage(hTemp, WM_COMMAND, IDCANCEL, 0); + else + kprintf("ERROR: zentaoamp is already running"); + } + return SYSERR; + } + } + + // For NT platforms, we could be a SERVICE or an APP + // + // SERVICE: an APP can't already be running. + // a SERVICE can't already be running. + // APP: a previous APP may already be running + // a SERVICE may already be running. + + if (platform) { + + // First try opening the Watchdog Event + + hWatchdogEvent = OpenEvent(EVENT_ALL_ACCESS, TRUE, "ZENTAOAMPWATCHDOG"); + + // Are we running as an APP or a SERVICE? + + if (service_arg) { + + // We're a SERVICE so open the events + + hStopEvent = OpenEvent(EVENT_ALL_ACCESS, TRUE, "ZENTAOAMPSTOP"); + hServiceStopEvent = OpenEvent(EVENT_ALL_ACCESS, TRUE, "ZENTAOAMPSERVICESTOP"); + + // No APP exists so continue normally + + xampp_service = 1; + xampp_app = 0; + return OK; + } + else { + + // We're an APP, so first check for another APP + + xampp_service = 0; + xampp_app = 1; + + if (hWatchdogEvent) { + + // An APP exists, so we can't continue + + kprintf("ERROR: zentaoamp is already running"); + return SYSERR; + } + + // We're an APP and no other APP exists so check for a SERVICE + + hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + + if (hSCM == 0) { + + // We can't get full access so try Read Access + + hSCM = OpenSCManager(NULL, NULL, GENERIC_READ); + + if (hSCM == 0) { + kprintf("ERROR: Read access to Service Control Manager denied"); + return SYSERR; + } + } + + hService = OpenService(hSCM, "zentaoamp", SERVICE_ALL_ACCESS); + if (hService == 0) { + CloseServiceHandle(hSCM); + return OK; // The SERVICE doesn't exist + } + + // The SERVICE exists and we have access + + if (ControlService(hService, SERVICE_CONTROL_INTERROGATE, &ss)) { + if (ss.dwCurrentState == SERVICE_RUNNING) { + if (kprintf("ERROR: zentaoamp is already running as a Service.\n\nClick OK to terminate the Service or Cancel to exit.") == IDCANCEL) { + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return SYSERR; + } + else { + result = ControlService(hService, SERVICE_CONTROL_STOP, &ss); + kprintf("SERVICE STOP %s", result?"SUCCEEDED.":"FAILED."); + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return OK; // The SERVICE has been stopped + } + } + } + else { + + // The SERVICE exists but isn't running + + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return OK; + } + + // The SERVICE may be in some other state + + kprintf("ERROR: zentaoamp Service State %x", ss.dwCurrentState); + + CloseServiceHandle(hService); + CloseServiceHandle(hSCM); + return SYSERR; + } + } + return OK; +} + +int ShowIcon() // called only if desktop interaction is desired +{ + while (Shell_NotifyIcon(NIM_ADD, &iconData) == FALSE) { + + // Hang around until the Desktop is up, otherwise autodial won't work + + if (icon_up) + break; // Just in case watchdog has already added the icon + + Sleep(2000); + } + return OK; +} + +int work(int argc, char **argv) { + + int i, j, m, n; + + update_flag = 1; + + // Handle icon for Service + + if (xampp_service) { + if (platform == 0) + ShowIcon(); // Waits until the Desktop is up + + if (platform) { + char value[256]; + char object[256]; + int type = 0; + + GetServiceValues(value, object, &type); + if (type & 0x100) + icon_up = 0; // An icon will be added by watchdog + else + icon_up = 1; // No icon will be added by watchdog + } + } + + GetCurrentDirectory(1024, cur_dir); + + GetInstallDirectory(1024, install_dir); + + // Initalize semaphores + + for (i=0; i 0) { + + // The SERVICE exists and is either running (2) or not (1) + + CheckDlgButton(hDlg, IDC_CHECKBOX0, BST_CHECKED); + job[0].service = 1; + + if (result == 2) { + SetDlgItemText(hDlg, IDC_EDIT0, ""); + SetDlgItemText(hDlg, IDC_ACTION0, "ֹͣ"); + job[0].state = 1; + } + else { + SetDlgItemText(hDlg, IDC_EDIT0, "ֹͣ"); + SetDlgItemText(hDlg, IDC_ACTION0, "80"); + job[0].state = 0; + } + } + else { + + job[0].service = 0; + + // Determine Apache APP status + + for (i=0; i 0) { + + // The SERVICE exists and is either running (2) or not (1) + + CheckDlgButton(hDlg, IDC_CHECKBOX0, BST_CHECKED); + job[2].service = 1; + + if (result == 2) { + SetDlgItemText(hDlg, IDC_EDIT0, ""); + SetDlgItemText(hDlg, IDC_ACTION2, "ֹͣ"); + job[2].state = 1; + } + else { + SetDlgItemText(hDlg, IDC_EDIT0, "ֹͣ"); + SetDlgItemText(hDlg, IDC_ACTION2, "88"); + job[2].state = 0; + } + } + else { + + job[2].service = 0; + + // Determine Apache APP status + + for (i=0; i 0) { + + // The SERVICE exists and is either running (2) or not (1) + + CheckDlgButton(hDlg, IDC_CHECKBOX1, BST_CHECKED); + job[1].service = 1; + + if (result == 2) { + SetDlgItemText(hDlg, IDC_EDIT1, ""); + SetDlgItemText(hDlg, IDC_ACTION1, "ֹͣ"); + job[1].state = 1; + } + else { + SetDlgItemText(hDlg, IDC_EDIT1, "ֹͣ"); + SetDlgItemText(hDlg, IDC_ACTION1, "3306"); + job[1].state = 0; + } + } + else { + + job[1].service = 0; + + // Determine MySql APP status + + for (i=0; i 0) { + + // The SERVICE exists and is either running (2) or not (1) + + CheckDlgButton(hDlg, IDC_CHECKBOX1, BST_CHECKED); + job[1].service = 1; + + if (result == 2) { + SetDlgItemText(hDlg, IDC_EDIT1, ""); + SetDlgItemText(hDlg, IDC_ACTION3, "ֹͣ"); + job[1].state = 1; + } + else { + SetDlgItemText(hDlg, IDC_EDIT1, "ֹͣ"); + SetDlgItemText(hDlg, IDC_ACTION3, "3308"); + job[1].state = 0; + } + } + else { + + job[1].service = 0; + + // Determine MySql APP status + + for (i=0; icmd, SW_HIDE, &exit_code, pjob->hSem, DETACHED_PROCESS); + + if (error) + return; + + hTmp = pjob->hThread; + pjob->hThread = 0; + CloseHandle(hTmp); + return; +} + +// +// execs - execute a program in the SYSTEM Directory +// +int execs(char *cmdline) +{ + BOOL result; + STARTUPINFO si; + PROCESS_INFORMATION pi; + SECURITY_ATTRIBUTES sa; + + char dir[256]; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + GetSystemDirectory(dir, 256); + + GetStartupInfo(&si); + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + result = CreateProcess( + (LPCTSTR) NULL, + (LPTSTR) cmdline, + (LPSECURITY_ATTRIBUTES) &sa, + (LPSECURITY_ATTRIBUTES) &sa, + (BOOL) FALSE, + (DWORD) DETACHED_PROCESS, + (LPVOID) NULL, + (LPCTSTR) dir, + (LPSTARTUPINFO) &si, + (LPPROCESS_INFORMATION) &pi); + + if (result) + return 0; + else + return GetLastError(); +} + +// +// exec - execute an application and don't wait for completion +// +int exec(char *cmdline, short flag) +{ + BOOL result; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + GetStartupInfo(&si); + + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = flag; + + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + result = CreateProcess( + (LPCTSTR) NULL, + (LPTSTR) cmdline, + (LPSECURITY_ATTRIBUTES) NULL, + (LPSECURITY_ATTRIBUTES) NULL, + (BOOL) FALSE, + (DWORD) CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS, + (LPVOID) NULL, + (LPCTSTR) NULL, + (LPSTARTUPINFO) &si, + (LPPROCESS_INFORMATION) &pi); + + if (result) + return 0; + + return GetLastError(); +} + +// +// execw - execute an application and wait for completion, signalling +// hSem after CreateProcess returns. +// +int execw(char *cmdline, int flag, DWORD *pexit, HANDLE hSem, DWORD dwCreationFlags) +{ + BOOL result; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + if (system_shutdown) { + if (hSem) + ReleaseSemaphore(hSem, 1, 0); + if (pexit) + *pexit = 1; + return 1; + } + + GetStartupInfo(&si); + + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = flag; + + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + result = CreateProcess( + (LPCTSTR) NULL, + (LPTSTR) cmdline, + (LPSECURITY_ATTRIBUTES) NULL, + (LPSECURITY_ATTRIBUTES) NULL, + (BOOL) FALSE, + (DWORD) dwCreationFlags, + (LPVOID) NULL, + (LPCTSTR) NULL, + (LPSTARTUPINFO) &si, + (LPPROCESS_INFORMATION) &pi); + + if (hSem) + ReleaseSemaphore(hSem, 1, 0); + + if (result) { + WaitForSingleObject(pi.hProcess, INFINITE); + if (pexit) + GetExitCodeProcess(pi.hProcess, pexit); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return 0; + } + else + return GetLastError(); +} + +// +// execwh - execute an application and wait for completion (hidden) +// +int execwh(char *cmdline) +{ + DWORD result; + STARTUPINFO si; + PROCESS_INFORMATION pi; + SECURITY_ATTRIBUTES sa; + + char dir[256]; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + GetCurrentDirectory(256, dir); + + GetStartupInfo(&si); + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + result = CreateProcess( + (LPCTSTR) NULL, + (LPTSTR) cmdline, + (LPSECURITY_ATTRIBUTES) &sa, + (LPSECURITY_ATTRIBUTES) &sa, + (BOOL) FALSE, + (DWORD) DETACHED_PROCESS, + (LPVOID) NULL, + (LPCTSTR) dir, + (LPSTARTUPINFO) &si, + (LPPROCESS_INFORMATION) &pi); + + if (result) { + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &result); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return result; + } + else + return GetLastError(); +} + +// +// sprintf -- print a formatted message to a buffer +// +int sprintf(char *buf, char *fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + + n = vsprintf(buf, fmt, args); + + va_end(args); + + if (n < 0) { + kprintf("sprintf error: %d", n);; + return SYSERR; + } + + buf[n] = 0; + + return n; +} +// +// printf - low-level printf to Console +// +int printf(char *fmt, ...) +{ + HANDLE hOut; + DWORD len, result; + va_list args; + char buf[0x8000]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + va_end(args); + + AllocConsole(); + hOut = GetStdHandle(STD_OUTPUT_HANDLE); + result = WriteFile(hOut, buf, strlen(buf), &len, NULL); + + CloseHandle(hOut); + return OK; +} + +// +// dprintf - low-level printf via OutputDebugString +// Run www.sysinternals.com DebugView.exe +// to view this output. +// +int dprintf(char *fmt, ...) +{ + va_list args; + char buf[0x8000]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + va_end(args); + + OutputDebugString(buf); + return OK; +} + +// +// kprintf - low-level printf +// +int kprintf(char *fmt, ...) +{ + va_list args; + char buf[0x8000]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + va_end(args); + + return MessageBoxEx(GetForegroundWindow(), buf, "zentaoamp Control", MB_OKCANCEL | MB_SETFOREGROUND | MB_ICONWARNING, 0); + +} + +// +// lbprintf - listbox printf +// +int lbprintf(HWND hList, char *fmt, ...) +{ + va_list args; + int i; + char buf[0x8000]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + va_end(args); + + i = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPCSTR) buf); + SendMessage(hList, LB_SETCARETINDEX, i, 0); + + return strlen(buf); + +} + +BOOL BottomRightWindow(HWND hWin) +{ + int result; + HWND hDesktop; + + RECT rc; + RECT rcd; + + hDesktop = GetDesktopWindow(); + result = GetWindowRect(hWin, &rc); + + if (result) + result = GetWindowRect(hDesktop, &rcd); + + if (result == 0) + return SYSERR; + + MoveWindow(hWin, + rcd.right - (rc.right - rc.left), + rcd.bottom - (rc.bottom - rc.top + 32), // 24 for task bar + rc.right - rc.left, rc.bottom - rc.top, + TRUE); + return OK; +} + + +BOOL CenterWindow(HWND hWin) +{ + int result; + HWND hDesktop; + + RECT rc; + RECT rcd; + + hDesktop = GetDesktopWindow(); + result = GetWindowRect(hWin, &rc); + + if (result) + result = GetWindowRect(hDesktop, &rcd); + + if (result == 0) + return -1; + + SetWindowPos(hWin, HWND_TOP, + rcd.right/2 - (rc.right - rc.left)/2, + rcd.bottom/2 - (rc.bottom - rc.top)/2, + 0, 0, + SWP_NOSIZE); + return 0; +} + +char TargetClass[1024]; +char TargetTitle[1024]; + +HWND hWindow; + +BOOL CALLBACK EnumWindowsProc(HWND hWnd, // handle to parent window + LPARAM lParam) // application-defined value +{ + char WinTitle[1024]; + char WinClass[1024]; + int clen, tlen; + DWORD pid; + DWORD tid; + + tid = GetWindowThreadProcessId(hWnd, &pid); + + tlen = GetWindowText(hWnd, WinTitle, 1023); + WinTitle[tlen] = 0; + + clen = GetClassName(hWnd, WinClass, 1023); + WinClass[clen] = 0; + + if (tlen && strlen(TargetTitle)) { + + if (stristr(strupr(WinTitle), strupr(TargetTitle))) { + + // Title matches so check class + + if (strlen(TargetClass) == 0) { + + // No target class so assume a match + + hWindow = hWnd; + return FALSE; + } + + if (stristr(strupr(WinClass), strupr(TargetClass))) { + + hWindow = hWnd; + return FALSE; + } + } + } + + if (clen && strlen(TargetClass)) { + + if (stristr(strupr(WinClass), strupr(TargetClass))) { + + // Class matches so check title + + if (strlen(TargetTitle) == 0) { + + // No target title so assume no match + + return TRUE; + } + + if (stristr(strupr(WinTitle), strupr(TargetTitle))) { + + hWindow = hWnd; + return FALSE; + } + } + } + + // Carry on + + return TRUE; +} + +int find(char *title) +{ + + strcpy(TargetTitle, title); + + EnumWindows(EnumWindowsProc, (LPARAM) 0); + + if (hWindow == 0) + return 0; + + ShowWindow(hWindow, SW_SHOWNORMAL); + + if (!IsWindowVisible(hWindow)) + ShowWindow(hWindow, SW_RESTORE); + + SetForegroundWindow(hWindow); // give focus to other window + + Sleep(2000); + + return 1; +} + +int refresh() { + + lbprintf(hList, "Refresh..."); + + UpdateStatus(hWnd); + + lbprintf(hList, "Done"); + + return OK; +} + +// Display the dialog box + +int EnterDialogBox() +{ + int result; + + result = DialogBox(hInst, "XAMPP", NULL, MyDlgProc); + + if (result == -1) { + kprintf("ERROR: unable to create zentaoamp dialog\n"); + return SYSERR; + } + + Shell_NotifyIcon(NIM_DELETE, &iconData); + + return OK; +} + +//run apache(80 88) and mysql(3306 3308) +int do_job(int m, int n) { + + int result; + struct job_entry *pjob = &job[m]; + + if (n == SYSERR) // NOOP + return OK; + + if (pjob->state == n) // nothing to do + return OK; + + lbprintf(hList, "Busy..."); + + switch (m) { + + case 0: //apache:80 + + if (n) { + localhost[16] = ':'; + localhost[17] = '8'; + localhost[18] = '0'; + localhost[19] = '\0'; + if (pjob->service) { + result = ServiceStart("Apache2.2"); + if (result == SYSERR) + lbprintf(hList, "ERROR: Apache service not started [%d]", result); + else + lbprintf(hList, "Apache service started"); + break; + } + + // strcpy(pjob->cmd, "apache\\bin\\apache.exe"); + if (pjob->service) { + strcpy(pjob->cmd, "apache\\bin\\httpd.exe -k start"); + } + else { + strcpy(pjob->cmd, "apache\\bin\\httpd.exe"); + } + + if (pjob->hThread) { + lbprintf(hList, "WARNING: terminating worker thread 0"); + TerminateThread(pjob->hThread, 0); + pjob->hThread = 0; + } + + pjob->hThread = CreateThread(0, 0, + (LPTHREAD_START_ROUTINE) Worker, + pjob, + 0, &pjob->dwTID); + + WaitForSingleObject(pjob->hSem, 5000); // Be sure execw has been called + + lbprintf(hList, "Apache started [Port %s]", apache_port80); + + } + else { + if (pjob->service) { + result = ServiceStop("Apache2.2"); + if (result == SYSERR) + lbprintf(hList, "ERROR: %d", result); + else + lbprintf(hList, "Apache service stopped"); + break; + } + else { + char cmd[1024]; + + // Kill the first Apache process + + sprintf(cmd, "apache\\bin\\pv.exe -f -k -q -i %d", pjob->dwPID); + + execw(cmd, SW_HIDE, 0, 0, DETACHED_PROCESS); + + UpdateStatus(0); + + // Kill the second Apache process + + sprintf(cmd, "apache\\bin\\pv.exe -f -k -q -i %d", pjob->dwPID); + + execw(cmd, SW_HIDE, 0, 0, DETACHED_PROCESS); + + lbprintf(hList, "Apache stopped [Port %s]", apache_port80); + } + } + + break; + case 2: //apache:88 + + if (n) { + localhost[16] = ':'; + localhost[17] = '8'; + localhost[18] = '8'; + localhost[19] = '\0'; + if (pjob->service) { + result = ServiceStart("Apache2.2"); + if (result == SYSERR) + lbprintf(hList, "ERROR: Apache service not started [%d]", result); + else + lbprintf(hList, "Apache service started"); + break; + } + + // strcpy(pjob->cmd, "apache\\bin\\apache.exe"); + if (pjob->service) { + strcpy(pjob->cmd, "apache\\bin\\httpd.exe -k start"); + } + else { + strcpy(pjob->cmd, "apache\\bin\\httpd.exe"); + } + + if (pjob->hThread) { + lbprintf(hList, "WARNING: terminating worker thread 0"); + TerminateThread(pjob->hThread, 0); + pjob->hThread = 0; + } + + pjob->hThread = CreateThread(0, 0, + (LPTHREAD_START_ROUTINE) Worker, + pjob, + 0, &pjob->dwTID); + + WaitForSingleObject(pjob->hSem, 5000); // Be sure execw has been called + + lbprintf(hList, "Apache started [Port %s]", apache_port88); + + } + else { + if (pjob->service) { + result = ServiceStop("Apache2.2"); + if (result == SYSERR) + lbprintf(hList, "ERROR: %d", result); + else + lbprintf(hList, "Apache service stopped"); + break; + } + else { + char cmd[1024]; + + // Kill the first Apache process + + sprintf(cmd, "apache\\bin\\pv.exe -f -k -q -i %d", pjob->dwPID); + + execw(cmd, SW_HIDE, 0, 0, DETACHED_PROCESS); + + UpdateStatus(0); + + // Kill the second Apache process + + sprintf(cmd, "apache\\bin\\pv.exe -f -k -q -i %d", pjob->dwPID); + + execw(cmd, SW_HIDE, 0, 0, DETACHED_PROCESS); + + lbprintf(hList, "Apache stopped [Port %s]", apache_port88); + } + } + + break; + + case 1: //mysql:3306 + + if (n) { + if (pjob->service) { + result = ServiceStart("mysql"); + if (result == SYSERR) + lbprintf(hList, "ERROR: MySql service not started [%d]", result); + else + lbprintf(hList, "MySql service started"); + break; + } + + strcpy(pjob->cmd, "mysql\\bin\\mysqld.exe --defaults-file=mysql\\bin\\my.ini --standalone"); + + if (pjob->hThread) { + lbprintf(hList, "WARNING: terminating worker thread 1"); + TerminateThread(pjob->hThread, 0); + pjob->hThread = 0; + } + + pjob->hThread = CreateThread(0, 0, + (LPTHREAD_START_ROUTINE) Worker, + pjob, + 0, &pjob->dwTID); + + WaitForSingleObject(pjob->hSem, 5000); // Be sure execw has been called + + lbprintf(hList, "MySql started [Port %s]", mysql_port3306); + } + else { + if (pjob->service) { + result = ServiceStop("mysql"); + if (result == SYSERR) + lbprintf(hList, "ERROR: MySql service not stopped [%d]", result); + else + lbprintf(hList, "MySql service stopped"); + break; + } + else { + int i; + char cmd[256]; + + // sprintf(cmd, "mysql\\bin\\mysqladmin.exe --port=%s -u root -h localhost shutdown", mysql_port); + sprintf(cmd, "apache\\bin\\pv.exe -f -k -q -i %d", pjob->dwPID); + result = execw(cmd, SW_HIDE, 0, 0, DETACHED_PROCESS); + + // Wait for MySql to actually terminate + + i = 20; + + while (i-- && pjob->state) { + UpdateStatus(0); + Sleep(500); + } + if (i < 0) + lbprintf(hList, "ERROR: MySql not stopped [%d]", result); + else + lbprintf(hList, "MySql stopped [Port %s]", mysql_port3306); + } + } + + break; + + case 3: //mysql:3308 + + if (n) { + if (pjob->service) { + result = ServiceStart("mysql"); + if (result == SYSERR) + lbprintf(hList, "ERROR: MySql service not started [%d]", result); + else + lbprintf(hList, "MySql service started"); + break; + } + + strcpy(pjob->cmd, "mysql\\bin\\mysqld.exe --defaults-file=mysql\\bin\\my.ini --standalone"); + + if (pjob->hThread) { + lbprintf(hList, "WARNING: terminating worker thread 1"); + TerminateThread(pjob->hThread, 0); + pjob->hThread = 0; + } + + pjob->hThread = CreateThread(0, 0, + (LPTHREAD_START_ROUTINE) Worker, + pjob, + 0, &pjob->dwTID); + + WaitForSingleObject(pjob->hSem, 5000); // Be sure execw has been called + + lbprintf(hList, "MySql started [Port %s]", mysql_port3308); + } + else { + if (pjob->service) { + result = ServiceStop("mysql"); + if (result == SYSERR) + lbprintf(hList, "ERROR: MySql service not stopped [%d]", result); + else + lbprintf(hList, "MySql service stopped"); + break; + } + else { + int i; + char cmd[256]; + + // sprintf(cmd, "mysql\\bin\\mysqladmin.exe --port=%s -u root -h localhost shutdown", mysql_port); + sprintf(cmd, "apache\\bin\\pv.exe -f -k -q -i %d", pjob->dwPID); + result = execw(cmd, SW_HIDE, 0, 0, DETACHED_PROCESS); + + // Wait for MySql to actually terminate + + i = 20; + + while (i-- && pjob->state) { + UpdateStatus(0); + Sleep(500); + } + if (i < 0) + lbprintf(hList, "ERROR: MySql not stopped [%d]", result); + else + lbprintf(hList, "MySql stopped [Port %s]", mysql_port3308); + } + } + + break; + + } + + strcpy(iconData.szTip, "Right-click to refresh"); + Shell_NotifyIcon(NIM_MODIFY, &iconData); + + return OK; +} + +BOOL APIENTRY ServiceDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int wmID, i, n, result, exit_code; + char value[1024]; + char tmp[512]; + char tmp1[512]; + char account[256] = ""; + char password[256] = ""; + char object[256] = ""; + char *ptr; + + switch (msg) { + + case WM_INITDIALOG: + + hServiceDlg = hDlg; + + CenterWindow(hDlg); + + if (xampp_service) + SetWindowText(hDlg, " [active]"); + else + SetWindowText(hDlg, ""); + + service_changed = 0; + + if (GetServiceValues(value, object, &service_type) == 0) { + + // Service is currently enabled + + service_enabled = 1; + + CheckDlgButton(hDlg, IDC_CHECKBOX9, 1); + + // Set checkboxes according to service parameters + + for (i=0; i + + +XAMPP Control Utility + + + + + + diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ZX.BAK b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ZX.BAK new file mode 100755 index 0000000000..328df178f4 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ZX.BAK @@ -0,0 +1,2 @@ +zip xampp.zip *.exe +move xampp.zip c:\ftproot diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ZX.BAT b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ZX.BAT new file mode 100755 index 0000000000..58573beb86 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/ZX.BAT @@ -0,0 +1,2 @@ +zip c:\ftproot\xampp25.zip *.* + diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/logo.bmp b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/logo.bmp new file mode 100755 index 0000000000..86b05a94e4 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/logo.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/resource.h b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/resource.h new file mode 100755 index 0000000000..2f517847a1 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/resource.h @@ -0,0 +1,17 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by XAMPP.RC +// +#define IDB_BITMAP1 110 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 111 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/screen.gif b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/screen.gif new file mode 100755 index 0000000000..9ddc971c7a Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/screen.gif differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp-icon.ico b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp-icon.ico new file mode 100755 index 0000000000..53f9a93e17 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp-icon.ico differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp.bmp b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp.bmp new file mode 100755 index 0000000000..0a41be994b Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp.bmp differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp.gif b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp.gif new file mode 100755 index 0000000000..71e44b5903 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp.gif differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.dsp b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.dsp new file mode 100755 index 0000000000..43dcae7522 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.dsp @@ -0,0 +1,155 @@ +# Microsoft Developer Studio Project File - Name="xampp_control" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=xampp_control - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "xampp_control.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "xampp_control.mak" CFG="xampp_control - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "xampp_control - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "xampp_control - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "xampp_control - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0xc09 /d "NDEBUG" +# ADD RSC /l 0xc09 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /machine:I386 +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Cmds=copy release\xampp_control.exe C:\xampp1\xampp\xampp.exe copy release\xampp_control.exe C:\xampp2\xampp\xampp.exe copy release\xampp_control.exe xampp.exe +# End Special Build Tool + +!ELSEIF "$(CFG)" == "xampp_control - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0xc09 /d "_DEBUG" +# ADD RSC /l 0xc09 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Cmds=copy debug\xampp_control.exe C:\xampp1\xampp\xampp.exe +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "xampp_control - Win32 Release" +# Name "xampp_control - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\XAMPP.C +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\XAMPP.H +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\BITMAP2.BMP +# End Source File +# Begin Source File + +SOURCE=.\ICON1.ICO +# End Source File +# Begin Source File + +SOURCE=.\logo.bmp +# End Source File +# Begin Source File + +SOURCE=.\TRAY.ICO +# End Source File +# Begin Source File + +SOURCE=.\xampp.bmp +# End Source File +# Begin Source File + +SOURCE=.\xampp.gif +# End Source File +# Begin Source File + +SOURCE=.\XAMPP.ICO +# End Source File +# Begin Source File + +SOURCE=.\XAMPP.RC +# End Source File +# End Group +# Begin Source File + +SOURCE=.\XAMPP.XML +# End Source File +# End Target +# End Project diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.dsw b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.dsw new file mode 100755 index 0000000000..745b5769f4 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "xampp_control"=.\xampp_control.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.ncb b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.ncb new file mode 100755 index 0000000000..ae44335b7a Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.ncb differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.opt b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.opt new file mode 100755 index 0000000000..36cecbf1a7 Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.opt differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.plg b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.plg new file mode 100755 index 0000000000..c5960718cf --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.plg @@ -0,0 +1,35 @@ + + +
    +

    Build Log

    +

    +--------------------Configuration: xampp_control - Win32 Debug-------------------- +

    +

    Command Lines

    +Creating temporary file "C:\DOCUME~1\yangyang\LOCALS~1\Temp\RSPDB.tmp" with contents +[ +kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /incremental:yes /pdb:"Debug/xampp_control.pdb" /debug /machine:I386 /out:"Debug/xampp_control.exe" /pdbtype:sept +".\Debug\XAMPP.OBJ" +".\Debug\XAMPP.res" +] +Creating command line "link.exe @C:\DOCUME~1\yangyang\LOCALS~1\Temp\RSPDB.tmp" +

    Output Window

    +Linking... +Creating temporary file "C:\DOCUME~1\yangyang\LOCALS~1\Temp\RSPDD.bat" with contents +[ +@echo off +copy debug\xampp_control.exe C:\xampp1\xampp\xampp.exe +] +Creating command line "C:\DOCUME~1\yangyang\LOCALS~1\Temp\RSPDD.bat" + +ϵͳҲָ· +Ѹ 0 ļ +ִ c:\windows\system32\cmd.exe ʱ. + + + +

    Results

    +xampp_control.exe - 1 error(s), 0 warning(s) +
    + + diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.sln b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.sln new file mode 100755 index 0000000000..93489dcc33 --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.sln @@ -0,0 +1,23 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xampp_control", "xampp_control.vcproj", "{038C5A0C-969D-4F21-8CE6-358CAABC0A37}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Debug.ActiveCfg = Debug|Win32 + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Debug.Build.0 = Debug|Win32 + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Release.ActiveCfg = Release|Win32 + {038C5A0C-969D-4F21-8CE6-358CAABC0A37}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.suo b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.suo new file mode 100755 index 0000000000..202e8b31ff Binary files /dev/null and b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.suo differ diff --git a/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.vcproj b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.vcproj new file mode 100755 index 0000000000..0bbf8b5fab --- /dev/null +++ b/trunk/build/windows/xampp/src/xampp-usb-lite/usb-control-panel/xampp_control.vcproj @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/trunk/build/windows/xampp/zentao.conf b/trunk/build/windows/xampp/zentao.conf new file mode 100644 index 0000000000..ff574d14c0 --- /dev/null +++ b/trunk/build/windows/xampp/zentao.conf @@ -0,0 +1,39 @@ +# add support for php. +LoadFile "/xampp/php/php5ts.dll" +LoadModule php5_module "/xampp/php/php5apache2_2.dll" + + SetHandler application/x-httpd-php + + + PHPINIDir "/xampp/php" + + +Alias /sqlbuddy "/xampp/admin/sqlbuddy/" + + Order deny,allow + Deny from all + Allow from ::1 127.0.0.0/8 + + +# adjust the mime settings. +AddType image/x-icon .ico +AddType image/gif .ico +AddType image/jpeg .jpg .jpeg +AddType image/png .png +AddType application/javascript .js + +# setting for zentao. +Alias /zentao "/xampp/zentao/www/" + + Options Indexes FollowSymLinks ExecCGI Includes + AllowOverride All + Order deny,allow + Allow from all + + +# setting of gzip. +DeflateCompressionLevel 9 +AddOutputFilterByType DEFLATE text/html text/css application/javascript + +# turn off etag. +FileEtag none diff --git a/trunk/build/windows/xampp/zentaoamp.exe b/trunk/build/windows/xampp/zentaoamp.exe new file mode 100755 index 0000000000..853091c1c3 Binary files /dev/null and b/trunk/build/windows/xampp/zentaoamp.exe differ diff --git a/trunk/config/config.php b/trunk/config/config.php new file mode 100644 index 0000000000..ea9df38104 --- /dev/null +++ b/trunk/config/config.php @@ -0,0 +1,121 @@ + + * @package config + * @version $Id$ + * @link http://www.zentao.net + */ +/* Basic settings. */ +$config->version = '3.3'; // The version of zentaopms. Don't change it. +$config->encoding = 'UTF-8'; // The encoding of zentaopms. +$config->cookieLife = time() + 2592000; // The cookie life time. +$config->timezone = 'Asia/Shanghai'; // The time zone setting, for more see http://www.php.net/manual/en/timezones.php +$config->webRoot = ''; // The root path of the pms. + +/* The request settings. */ +$config->requestType = 'PATH_INFO'; // The request type: PATH_INFO|GET, if PATH_INFO, must use url rewrite. +$config->pathType = 'clean'; // If the request type is PATH_INFO, the path type. +$config->requestFix = '-'; // The divider in the url when PATH_INFO. +$config->moduleVar = 'm'; // requestType=GET: the module var name. +$config->methodVar = 'f'; // requestType=GET: the method var name. +$config->viewVar = 't'; // requestType=GET: the view var name. +$config->sessionVar = 'sid'; // requestType=GET: the session var name. + +/* Supported views. */ +$config->views = ',html,json,'; + +/* Set the wide window size. */ +$config->wideSize = 1400; + +/* Supported languages. */ +$config->langs['zh-cn'] = '中文简体'; +$config->langs['zh-tw'] = '中文繁體'; +$config->langs['en'] = 'English'; + +/* Default settings. */ +$config->default->view = 'html'; // Default view. +$config->default->lang = 'en'; // Default language. +$config->default->theme = 'default'; // Default theme. +$config->default->module = 'index'; // Default module. +$config->default->method = 'index'; // Default method. + +/* Upload settings. */ +$config->file->dangers = 'php,jsp,py,rb,asp,'; // Dangerous files. +$config->file->maxSize = 1024 * 1024; // Max size. + +/* Master database settings. */ +$config->db->persistant = false; // Pconnect or not. +$config->db->driver = 'mysql'; // Must be MySQL. Don't support other database server yet. +$config->db->encoding = 'UTF8'; // Encoding of database. +$config->db->strictMode = false; // Turn off the strict mode of MySQL. +//$config->db->emulatePrepare = true; // PDO::ATTR_EMULATE_PREPARES +//$config->db->bufferQuery = true; // PDO::MYSQL_ATTR_USE_BUFFERED_QUERY + +/* Slave database settings. */ +$config->slaveDB->persistant = false; +$config->slaveDB->driver = 'mysql'; +$config->slaveDB->encoding = 'UTF8'; +$config->slaveDB->strictMode = false; +$config->slaveDB->checkCentOS= true; + +/* Include the custom config file. */ +$configRoot = dirname(__FILE__) . DIRECTORY_SEPARATOR; +$myConfig = $configRoot . 'my.php'; +if(file_exists($myConfig)) include $myConfig; + +/* Set default table prefix. */ +if(!isset($config->db->prefix)) $config->db->prefix = 'zt_'; + +/* Include extension config files. */ +$extConfigFiles = glob($configRoot . 'ext/*.php'); +if($extConfigFiles) foreach($extConfigFiles as $extConfigFile) include $extConfigFile; + +/* Define the tables. */ +define('TABLE_COMPANY', '`' . $config->db->prefix . 'company`'); +define('TABLE_DEPT', '`' . $config->db->prefix . 'dept`'); +define('TABLE_CONFIG', '`' . $config->db->prefix . 'config`'); +define('TABLE_USER', '`' . $config->db->prefix . 'user`'); +define('TABLE_TODO', '`' . $config->db->prefix . 'todo`'); +define('TABLE_GROUP', '`' . $config->db->prefix . 'group`'); +define('TABLE_GROUPPRIV', '`' . $config->db->prefix . 'groupPriv`'); +define('TABLE_USERGROUP', '`' . $config->db->prefix . 'userGroup`'); +define('TABLE_USERQUERY', '`' . $config->db->prefix . 'userQuery`'); + +define('TABLE_BUG', '`' . $config->db->prefix . 'bug`'); +define('TABLE_CASE', '`' . $config->db->prefix . 'case`'); +define('TABLE_CASESTEP', '`' . $config->db->prefix . 'caseStep`'); +define('TABLE_TESTTASK', '`' . $config->db->prefix . 'testTask`'); +define('TABLE_TESTRUN', '`' . $config->db->prefix . 'testRun`'); +define('TABLE_TESTRESULT', '`' . $config->db->prefix . 'testResult`'); +define('TABLE_USERTPL', '`' . $config->db->prefix . 'userTPL`'); + +define('TABLE_PRODUCT', '`' . $config->db->prefix . 'product`'); +define('TABLE_STORY', '`' . $config->db->prefix . 'story`'); +define('TABLE_STORYSPEC', '`' . $config->db->prefix . 'storySpec`'); +define('TABLE_PRODUCTPLAN', '`' . $config->db->prefix . 'productPlan`'); +define('TABLE_RELEASE', '`' . $config->db->prefix . 'release`'); + +define('TABLE_PROJECT', '`' . $config->db->prefix . 'project`'); +define('TABLE_TASK', '`' . $config->db->prefix . 'task`'); +define('TABLE_TEAM', '`' . $config->db->prefix . 'team`'); +define('TABLE_PROJECTPRODUCT','`' . $config->db->prefix . 'projectProduct`'); +define('TABLE_PROJECTSTORY', '`' . $config->db->prefix . 'projectStory`'); +define('TABLE_TASKESTIMATE', '`' . $config->db->prefix . 'taskEstimate`'); +define('TABLE_EFFORT', '`' . $config->db->prefix . 'effort`'); +define('TABLE_BURN', '`' . $config->db->prefix . 'burn`'); +define('TABLE_BUILD', '`' . $config->db->prefix . 'build`'); + +define('TABLE_DOCLIB', '`' . $config->db->prefix . 'docLib`'); +define('TABLE_DOC', '`' . $config->db->prefix . 'doc`'); + +define('TABLE_MODULE', '`' . $config->db->prefix . 'module`'); +define('TABLE_ACTION', '`' . $config->db->prefix . 'action`'); +define('TABLE_FILE', '`' . $config->db->prefix . 'file`'); +define('TABLE_HISTORY', '`' . $config->db->prefix . 'history`'); +define('TABLE_EXTENSION', '`' . $config->db->prefix . 'extension`'); diff --git a/trunk/db/demo.sql b/trunk/db/demo.sql new file mode 100644 index 0000000000..16281bd8be --- /dev/null +++ b/trunk/db/demo.sql @@ -0,0 +1,313 @@ +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(1, 1, 'user', 20, ',0,', 0, 'azhi', 'logout', '2012-06-05 09:24:52', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(2, 1, 'user', 1, ',0,', 0, 'admin', 'login', '2012-06-05 09:25:00', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(3, 1, 'user', 1, ',0,', 0, 'admin', 'logout', '2012-06-05 09:51:10', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(4, 1, 'user', 2, ',0,', 0, 'productManager', 'login', '2012-06-05 09:51:33', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(5, 1, 'user', 2, ',0,', 0, 'productManager', 'logout', '2012-06-05 09:53:05', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(6, 1, 'user', 1, ',0,', 0, 'admin', 'login', '2012-06-05 09:53:10', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(7, 1, 'user', 1, ',0,', 0, 'admin', 'logout', '2012-06-05 09:53:52', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(8, 1, 'user', 2, ',0,', 0, 'productManager', 'login', '2012-06-05 09:54:07', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(9, 1, 'product', 1, ',1,', 0, 'productManager', 'opened', '2012-06-05 09:57:07', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(10, 1, 'productplan', 1, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:02:49', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(11, 1, 'story', 1, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:09:49', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(12, 1, 'story', 2, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:16:37', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(13, 1, 'story', 3, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:18:10', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(14, 1, 'story', 3, ',1,', 0, 'productManager', 'changed', '2012-06-05 10:19:06', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(15, 1, 'story', 4, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:20:16', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(16, 1, 'story', 5, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:21:39', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(17, 1, 'story', 6, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:23:11', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(18, 1, 'story', 7, ',1,', 0, 'productManager', 'opened', '2012-06-05 10:24:19', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(19, 1, 'story', 1, ',1,', 0, 'productManager', 'reviewed', '2012-06-05 10:25:19', '', 'Pass'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(20, 1, 'story', 2, ',1,', 0, 'productManager', 'reviewed', '2012-06-05 10:25:33', '', 'Pass'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(21, 1, 'story', 3, ',1,', 0, 'productManager', 'reviewed', '2012-06-05 10:25:38', '', 'Pass'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(22, 1, 'story', 4, ',1,', 0, 'productManager', 'reviewed', '2012-06-05 10:25:42', '', 'Pass'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(23, 1, 'user', 2, ',0,', 0, 'productManager', 'logout', '2012-06-05 10:26:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(24, 1, 'user', 3, ',0,', 0, 'projectManager', 'login', '2012-06-05 10:26:38', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(25, 1, 'project', 1, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:28:36', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(26, 1, 'story', 4, ',1,', 1, 'projectManager', 'linked2project', '2012-06-05 10:31:02', '', '1'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(27, 1, 'story', 3, ',1,', 1, 'projectManager', 'linked2project', '2012-06-05 10:31:02', '', '1'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(28, 1, 'story', 2, ',1,', 1, 'projectManager', 'linked2project', '2012-06-05 10:31:02', '', '1'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(29, 1, 'story', 1, ',1,', 1, 'projectManager', 'linked2project', '2012-06-05 10:31:02', '', '1'); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(30, 1, 'task', 1, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:32:03', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(31, 1, 'task', 2, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:32:23', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(32, 1, 'task', 3, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:33:01', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(33, 1, 'task', 4, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:33:21', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(34, 1, 'task', 5, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:33:44', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(35, 1, 'task', 6, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:33:59', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(36, 1, 'task', 7, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:34:25', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(37, 1, 'task', 8, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:34:45', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(38, 1, 'task', 9, ',1,', 1, 'projectManager', 'opened', '2012-06-05 10:35:01', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(39, 1, 'user', 3, ',0,', 0, 'projectManager', 'logout', '2012-06-05 10:37:30', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(40, 1, 'user', 4, ',0,', 0, 'dev1', 'login', '2012-06-05 10:37:40', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(41, 1, 'task', 1, ',1,', 1, 'dev1', 'started', '2012-06-05 10:37:54', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(42, 1, 'task', 1, ',1,', 1, 'dev1', 'finished', '2012-06-05 10:38:00', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(43, 1, 'task', 8, ',1,', 1, 'dev1', 'finished', '2012-06-05 10:39:14', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(44, 1, 'task', 9, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(45, 1, 'task', 8, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(46, 1, 'task', 7, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(47, 1, 'task', 6, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(48, 1, 'task', 5, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(49, 1, 'task', 4, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(50, 1, 'task', 3, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(51, 1, 'task', 2, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(52, 1, 'task', 1, ',1,', 1, 'dev1', 'edited', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(53, 1, 'user', 4, ',0,', 0, 'dev1', 'logout', '2012-06-05 10:41:50', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(54, 1, 'user', 5, ',0,', 0, 'dev2', 'login', '2012-06-05 10:41:56', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(55, 1, 'task', 4, ',1,', 1, 'dev2', 'edited', '2012-06-05 10:42:56', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(56, 1, 'task', 3, ',1,', 1, 'dev2', 'edited', '2012-06-05 10:42:57', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(57, 1, 'user', 5, ',0,', 0, 'dev2', 'logout', '2012-06-05 10:43:02', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(58, 1, 'user', 6, ',0,', 0, 'dev3', 'login', '2012-06-05 10:43:07', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(59, 1, 'task', 6, ',1,', 1, 'dev3', 'edited', '2012-06-05 10:43:32', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(60, 1, 'task', 5, ',1,', 1, 'dev3', 'edited', '2012-06-05 10:43:32', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(61, 1, 'user', 6, ',0,', 0, 'dev3', 'logout', '2012-06-05 10:43:42', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(62, 1, 'user', 3, ',0,', 0, 'projectManager', 'login', '2012-06-05 10:44:05', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(63, 1, 'user', 3, ',0,', 0, 'projectManager', 'logout', '2012-06-05 10:50:03', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(64, 1, 'user', 10, ',0,', 0, 'testManager', 'login', '2012-06-05 10:50:09', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(65, 1, 'user', 10, ',0,', 0, 'testManager', 'logout', '2012-06-05 10:50:14', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(66, 1, 'user', 10, ',0,', 0, 'testManager', 'login', '2012-06-05 10:50:23', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(67, 1, 'user', 10, ',0,', 0, 'testManager', 'logout', '2012-06-05 10:50:32', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(68, 1, 'user', 1, ',0,', 0, 'admin', 'login', '2012-06-05 10:50:36', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(69, 1, 'user', 1, ',0,', 0, 'admin', 'logout', '2012-06-05 10:50:53', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(70, 1, 'user', 10, ',0,', 0, 'testManager', 'login', '2012-06-05 10:51:01', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(71, 1, 'user', 10, ',0,', 0, 'testManager', 'logout', '2012-06-05 10:51:33', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(72, 1, 'user', 7, ',0,', 0, 'tester1', 'login', '2012-06-05 10:51:38', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(73, 1, 'bug', 1, ',1,', 1, 'tester1', 'opened', '2012-06-05 10:56:11', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(74, 1, 'bug', 2, ',1,', 1, 'tester1', 'opened', '2012-06-05 10:57:11', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(75, 1, 'user', 7, ',0,', 0, 'tester1', 'logout', '2012-06-05 10:57:13', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(76, 1, 'user', 8, ',0,', 0, 'tester2', 'login', '2012-06-05 10:57:24', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(77, 1, 'bug', 3, ',1,', 1, 'tester2', 'opened', '2012-06-05 10:58:22', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(78, 1, 'user', 8, ',0,', 0, 'tester2', 'logout', '2012-06-05 10:58:39', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(79, 1, 'user', 9, ',0,', 0, 'tester3', 'login', '2012-06-05 10:58:46', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(80, 1, 'bug', 4, ',1,', 1, 'tester3', 'opened', '2012-06-05 11:00:19', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(81, 1, 'case', 1, ',1,', 0, 'tester3', 'opened', '2012-06-05 11:02:18', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(82, 1, 'case', 1, ',1,', 0, 'tester3', 'edited', '2012-06-05 11:02:47', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(83, 1, 'user', 9, ',0,', 0, 'tester3', 'logout', '2012-06-05 11:02:48', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(84, 1, 'user', 7, ',0,', 0, 'tester1', 'login', '2012-06-05 11:02:56', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(85, 1, 'case', 2, ',1,', 0, 'tester1', 'opened', '2012-06-05 11:03:28', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(86, 1, 'case', 3, ',1,', 0, 'tester1', 'opened', '2012-06-05 11:03:54', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(87, 1, 'user', 7, ',0,', 0, 'tester1', 'logout', '2012-06-05 11:04:00', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(88, 1, 'user', 8, ',0,', 0, 'tester2', 'login', '2012-06-05 11:04:10', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(89, 1, 'case', 4, ',1,', 0, 'tester2', 'opened', '2012-06-05 11:04:48', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(90, 1, 'user', 8, ',0,', 0, 'tester2', 'logout', '2012-06-05 11:04:52', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(91, 1, 'user', 10, ',0,', 0, 'testManager', 'login', '2012-06-05 11:04:58', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(92, 1, 'testtask', 1, ',1,', 1, 'testManager', 'opened', '2012-06-05 11:07:41', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(93, 1, 'testtask', 1, ',1,', 1, 'testManager', 'edited', '2012-06-05 11:07:52', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(94, 1, 'user', 10, ',0,', 0, 'testManager', 'logout', '2012-06-05 11:08:10', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(95, 1, 'user', 1, ',0,', 0, 'admin', 'login', '2012-06-05 11:08:15', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(96, 1, 'user', 1, ',0,', 0, 'admin', 'logout', '2012-06-05 11:08:23', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(97, 1, 'user', 10, ',0,', 0, 'testManager', 'login', '2012-06-05 11:08:35', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(98, 1, 'user', 10, ',0,', 0, 'testManager', 'logout', '2012-06-05 11:08:55', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(99, 1, 'user', 7, ',0,', 0, 'tester1', 'login', '2012-06-05 11:08:59', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(100, 1, 'user', 7, ',0,', 0, 'tester1', 'logout', '2012-06-05 11:09:52', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(101, 1, 'user', 1, ',0,', 0, 'admin', 'login', '2012-06-05 11:09:54', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(102, 1, 'user', 1, ',0,', 0, 'admin', 'logout', '2012-06-05 11:10:38', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(103, 1, 'user', 10, ',0,', 0, 'testManager', 'login', '2012-06-05 11:10:42', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(104, 1, 'user', 10, ',0,', 0, 'testManager', 'logout', '2012-06-05 11:11:13', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(105, 1, 'user', 3, ',0,', 0, 'projectManager', 'login', '2012-06-05 11:11:16', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(106, 1, 'build', 1, ',1,', 1, 'projectManager', 'opened', '2012-06-05 11:11:45', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(107, 1, 'project', 2, ',1,', 2, 'projectManager', 'opened', '2012-06-05 11:12:28', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(108, 1, 'user', 3, ',0,', 0, 'projectManager', 'logout', '2012-06-05 11:14:40', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(109, 1, 'user', 2, ',0,', 0, 'productManager', 'login', '2012-06-05 11:14:43', '', ''); +INSERT INTO `zt_action` (`id`, `company`, `objectType`, `objectID`, `product`, `project`, `actor`, `action`, `date`, `comment`, `extra`) VALUES(110, 1, 'product', 2, ',2,', 0, 'productManager', 'opened', '2012-06-05 11:15:20', '', ''); + +INSERT INTO `zt_bug` (`id`, `company`, `product`, `module`, `project`, `story`, `storyVersion`, `task`, `toTask`, `toStory`, `title`, `keywords`, `severity`, `pri`, `type`, `os`, `browser`, `hardware`, `found`, `steps`, `status`, `confirmed`, `activatedCount`, `mailto`, `openedBy`, `openedDate`, `openedBuild`, `assignedTo`, `assignedDate`, `resolvedBy`, `resolution`, `resolvedBuild`, `resolvedDate`, `closedBy`, `closedDate`, `duplicateBug`, `linkBug`, `case`, `caseVersion`, `result`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(1, 1, 1, 8, 1, 1, 1, 1, 0, 0, '首页页面问题', '', 3, 0, 'interface', '', '', '', '', '

    [步骤]进入首页

    \r\n

    [结果]出现乱码    

    \r\n

    [期望]正常显示

    ', 'active', 0, 0, '', 'tester1', '2012-06-05 10:56:11', 'trunk', 'dev1', '2012-06-05 10:56:11', '', '', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', 0, '', 0, 1, 0, '', '0000-00-00 00:00:00', '0'); +INSERT INTO `zt_bug` (`id`, `company`, `product`, `module`, `project`, `story`, `storyVersion`, `task`, `toTask`, `toStory`, `title`, `keywords`, `severity`, `pri`, `type`, `os`, `browser`, `hardware`, `found`, `steps`, `status`, `confirmed`, `activatedCount`, `mailto`, `openedBy`, `openedDate`, `openedBuild`, `assignedTo`, `assignedDate`, `resolvedBy`, `resolution`, `resolvedBuild`, `resolvedDate`, `closedBy`, `closedDate`, `duplicateBug`, `linkBug`, `case`, `caseVersion`, `result`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(2, 1, 1, 9, 1, 2, 1, 4, 0, 0, '新闻中心页面问题', '', 3, 0, 'codeerror', '', '', '', '', '

    [步骤]进入新闻中心

    \r\n

    [结果]页面出现乱码

    \r\n

    [期望]正常显示

    ', 'active', 0, 0, '', 'tester1', '2012-06-05 10:57:11', 'trunk', 'dev2', '2012-06-05 10:57:11', '', '', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', 0, '', 0, 1, 0, '', '0000-00-00 00:00:00', '0'); +INSERT INTO `zt_bug` (`id`, `company`, `product`, `module`, `project`, `story`, `storyVersion`, `task`, `toTask`, `toStory`, `title`, `keywords`, `severity`, `pri`, `type`, `os`, `browser`, `hardware`, `found`, `steps`, `status`, `confirmed`, `activatedCount`, `mailto`, `openedBy`, `openedDate`, `openedBuild`, `assignedTo`, `assignedDate`, `resolvedBy`, `resolution`, `resolvedBuild`, `resolvedDate`, `closedBy`, `closedDate`, `duplicateBug`, `linkBug`, `case`, `caseVersion`, `result`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(3, 1, 1, 10, 1, 3, 2, 6, 0, 0, '成果展示页面问题', '', 3, 0, 'codeerror', '', '', '', '', '

    [步骤]进入成果展示    

    \r\n

    [结果]乱码

    \r\n

    [期望]正常显示

    ', 'active', 0, 0, '', 'tester2', '2012-06-05 10:58:22', 'trunk', 'dev3', '2012-06-05 10:58:22', '', '', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', 0, '', 0, 1, 0, '', '0000-00-00 00:00:00', '0'); +INSERT INTO `zt_bug` (`id`, `company`, `product`, `module`, `project`, `story`, `storyVersion`, `task`, `toTask`, `toStory`, `title`, `keywords`, `severity`, `pri`, `type`, `os`, `browser`, `hardware`, `found`, `steps`, `status`, `confirmed`, `activatedCount`, `mailto`, `openedBy`, `openedDate`, `openedBuild`, `assignedTo`, `assignedDate`, `resolvedBy`, `resolution`, `resolvedBuild`, `resolvedDate`, `closedBy`, `closedDate`, `duplicateBug`, `linkBug`, `case`, `caseVersion`, `result`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(4, 1, 1, 11, 1, 4, 1, 9, 0, 0, '售后服务页面问题', '', 3, 0, 'codeerror', '', '', '', '', '

    [步骤]进入售后服务

    \r\n

    [结果]乱码

    \r\n

    [期望]正常显示

    ', 'active', 0, 0, '', 'tester3', '2012-06-05 11:00:19', 'trunk', 'dev1', '2012-06-05 11:00:19', '', '', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', 0, '', 0, 1, 0, '', '0000-00-00 00:00:00', '0'); + +INSERT INTO `zt_build` (`id`, `company`, `product`, `project`, `name`, `scmPath`, `filePath`, `date`, `stories`, `bugs`, `builder`, `desc`, `deleted`) VALUES(1, 1, 1, 1, '第一期版本', '', '', '2012-06-05', '3,2,1,4', '', 'projectManager', '', '0'); + +INSERT INTO `zt_burn` (`company`, `project`, `date`, `left`, `consumed`) VALUES(1, 1, '2012-06-05', 0, 38); + +INSERT INTO `zt_case` (`id`, `company`, `product`, `module`, `path`, `story`, `storyVersion`, `title`, `precondition`, `keywords`, `pri`, `type`, `stage`, `howRun`, `scriptedBy`, `scriptedDate`, `scriptStatus`, `scriptLocation`, `status`, `frequency`, `order`, `openedBy`, `openedDate`, `lastEditedBy`, `lastEditedDate`, `version`, `linkCase`, `fromBug`, `deleted`, `lastRunner`, `lastRunDate`, `lastRunResult`) VALUES(1, 1, 1, 0, 0, 4, 1, '售后服务的测试用例', '', '', 1, 'feature', 'feature', '', '', '0000-00-00', '', '', 'normal', '1', 0, 'tester3', '2012-06-05 11:02:18', 'tester3', '2012-06-05 11:02:46', 1, '', 0, '0', 'testManager', '2012-06-05 11:11:00', 'pass'); +INSERT INTO `zt_case` (`id`, `company`, `product`, `module`, `path`, `story`, `storyVersion`, `title`, `precondition`, `keywords`, `pri`, `type`, `stage`, `howRun`, `scriptedBy`, `scriptedDate`, `scriptStatus`, `scriptLocation`, `status`, `frequency`, `order`, `openedBy`, `openedDate`, `lastEditedBy`, `lastEditedDate`, `version`, `linkCase`, `fromBug`, `deleted`, `lastRunner`, `lastRunDate`, `lastRunResult`) VALUES(2, 1, 1, 0, 0, 1, 1, '首页的测试用例', '', '', 3, 'feature', '', '', '', '0000-00-00', '', '', 'normal', '1', 0, 'tester1', '2012-06-05 11:03:28', '', '0000-00-00 00:00:00', 1, '', 0, '0', 'testManager', '2012-06-05 11:11:05', 'pass'); +INSERT INTO `zt_case` (`id`, `company`, `product`, `module`, `path`, `story`, `storyVersion`, `title`, `precondition`, `keywords`, `pri`, `type`, `stage`, `howRun`, `scriptedBy`, `scriptedDate`, `scriptStatus`, `scriptLocation`, `status`, `frequency`, `order`, `openedBy`, `openedDate`, `lastEditedBy`, `lastEditedDate`, `version`, `linkCase`, `fromBug`, `deleted`, `lastRunner`, `lastRunDate`, `lastRunResult`) VALUES(3, 1, 1, 0, 0, 2, 1, '新闻中心的测试用例', '', '', 3, 'feature', 'feature', '', '', '0000-00-00', '', '', 'normal', '1', 0, 'tester1', '2012-06-05 11:03:54', '', '0000-00-00 00:00:00', 1, '', 0, '0', 'testManager', '2012-06-05 11:11:07', 'pass'); +INSERT INTO `zt_case` (`id`, `company`, `product`, `module`, `path`, `story`, `storyVersion`, `title`, `precondition`, `keywords`, `pri`, `type`, `stage`, `howRun`, `scriptedBy`, `scriptedDate`, `scriptStatus`, `scriptLocation`, `status`, `frequency`, `order`, `openedBy`, `openedDate`, `lastEditedBy`, `lastEditedDate`, `version`, `linkCase`, `fromBug`, `deleted`, `lastRunner`, `lastRunDate`, `lastRunResult`) VALUES(4, 1, 1, 0, 0, 3, 2, '成果展示测试用例', '', '', 3, 'feature', 'feature', '', '', '0000-00-00', '', '', 'normal', '1', 0, 'tester2', '2012-06-05 11:04:48', '', '0000-00-00 00:00:00', 1, '', 0, '0', 'testManager', '2012-06-05 11:11:08', 'pass'); + +INSERT INTO `zt_caseStep` (`id`, `company`, `case`, `version`, `desc`, `expect`) VALUES(1, 1, 1, 1, '进入首页', '正常显示'); +INSERT INTO `zt_caseStep` (`id`, `company`, `case`, `version`, `desc`, `expect`) VALUES(2, 1, 2, 1, '进入首页', '正常显示'); +INSERT INTO `zt_caseStep` (`id`, `company`, `case`, `version`, `desc`, `expect`) VALUES(3, 1, 3, 1, '进入新闻中心', '正常显示'); +INSERT INTO `zt_caseStep` (`id`, `company`, `case`, `version`, `desc`, `expect`) VALUES(4, 1, 4, 1, '进入成果展示', '正常显示'); + +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(1, 1, '经理', 0, ',1,', 1, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(2, 1, '开发', 0, ',2,', 1, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(3, 1, '测试', 0, ',3,', 1, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(4, 1, '市场', 0, ',4,', 1, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(5, 1, '产品', 1, ',1,5,', 2, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(6, 1, '项目', 1, ',1,6,', 2, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(7, 1, '编程', 2, ',2,7,', 2, 0, '', '', ''); +INSERT INTO `zt_dept` (`id`, `company`, `name`, `parent`, `path`, `grade`, `order`, `position`, `function`, `manager`) VALUES(8, 1, '美工', 2, ',2,8,', 2, 0, '', '', ''); + + +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(1, 1, 14, 'version', '1', '2', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(2, 1, 14, 'spec', ' 作为一名本公司的用户,我希望可以在成果展示看到该公司网站的企业新闻,这样可以方便我进行了解该公司的产品并进行购买。 
    ', ' 作为一名本公司的用户,我希望可以在成果展示看到该公司网站的吹产品,这样可以方便我进行了解该公司的产品并进行购买。 
    ', '001- 作为一名本公司的用户,我希望可以在成果展示看到该公司网站的企业新闻,这样可以方便我进行了解该公司的产品并进行购买。
    \n001+ 作为一名本公司的用户,我希望可以在成果展示看到该公司网站的吹产品,这样可以方便我进行了解该公司的产品并进行购买。
    '); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(3, 1, 41, 'consumed', '0', '1', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(4, 1, 41, 'status', 'wait', 'doing', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(5, 1, 41, 'statusCustom', '1', '6', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(6, 1, 42, 'consumed', '1', '7', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(7, 1, 42, 'left', '10', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(8, 1, 42, 'assignedTo', 'dev1', 'projectManager', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(9, 1, 42, 'status', 'doing', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(10, 1, 42, 'finishedBy', '', 'dev1', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(11, 1, 42, 'finishedDate', '', '2012-06-05 10:38:00', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(12, 1, 42, 'statusCustom', '6', '12', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(13, 1, 43, 'consumed', '0', '6', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(14, 1, 43, 'left', '8', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(15, 1, 43, 'assignedTo', 'dev1', 'projectManager', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(16, 1, 43, 'status', 'wait', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(17, 1, 43, 'finishedBy', '', 'dev1', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(18, 1, 43, 'finishedDate', '', '2012-06-05 10:39:14', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(19, 1, 43, 'statusCustom', '1', '12', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(20, 1, 44, 'canceledDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(21, 1, 45, 'canceledDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(22, 1, 46, 'canceledDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(23, 1, 47, 'closedDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(24, 1, 48, 'closedDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(25, 1, 49, 'closedDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(26, 1, 50, 'closedDate', '2012-06-05 10:41:12', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(27, 1, 51, 'status', 'wait', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(28, 1, 51, 'consumed', '0', '8', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(29, 1, 51, 'left', '10', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(30, 1, 51, 'finishedBy', '', 'dev1', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(31, 1, 51, 'finishedDate', '', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(32, 1, 52, 'finishedDate', '2012-06-05 10:38:00', '2012-06-05 10:41:20', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(33, 1, 55, 'status', 'closed', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(34, 1, 55, 'consumed', '0', '5', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(35, 1, 55, 'left', '8', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(36, 1, 55, 'finishedBy', '', 'dev2', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(37, 1, 55, 'closedBy', 'dev1', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(38, 1, 55, 'closedReason', 'done', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(39, 1, 55, 'finishedDate', '', '2012-06-05 10:42:56', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(40, 1, 55, 'closedDate', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(41, 1, 56, 'status', 'closed', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(42, 1, 56, 'consumed', '0', '8', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(43, 1, 56, 'left', '8', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(44, 1, 56, 'finishedBy', '', 'dev2', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(45, 1, 56, 'closedBy', 'dev1', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(46, 1, 56, 'closedReason', 'done', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(47, 1, 56, 'finishedDate', '', '2012-06-05 10:42:56', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(48, 1, 56, 'closedDate', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(49, 1, 59, 'status', 'closed', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(50, 1, 59, 'consumed', '0', '5', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(51, 1, 59, 'left', '8', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(52, 1, 59, 'finishedBy', 'dev1', 'dev3', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(53, 1, 59, 'closedBy', 'dev1', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(54, 1, 59, 'closedReason', 'done', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(55, 1, 59, 'finishedDate', '', '2012-06-05 10:43:32', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(56, 1, 59, 'closedDate', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(57, 1, 60, 'status', 'closed', 'done', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(58, 1, 60, 'consumed', '0', '5', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(59, 1, 60, 'left', '8', '0', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(60, 1, 60, 'finishedBy', 'dev1', 'dev3', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(61, 1, 60, 'closedBy', 'dev1', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(62, 1, 60, 'closedReason', 'done', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(63, 1, 60, 'finishedDate', '', '2012-06-05 10:43:32', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(64, 1, 60, 'closedDate', '2012-06-05 10:41:20', '', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(65, 1, 82, 'title', '首页的测试用例', '售后服务的测试用例', '001- 首页的测试用例\n001+ 售后服务的测试用例'); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(66, 1, 82, 'story', '1', '4', ''); +INSERT INTO `zt_history` (`id`, `company`, `action`, `field`, `old`, `new`, `diff`) VALUES(67, 1, 93, 'build', '', 'trunk', ''); + +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(1, 1, 1, '首页', 0, ',1,', 1, 10, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(2, 1, 1, '新闻中心', 0, ',2,', 1, 20, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(3, 1, 1, '成果展示', 0, ',3,', 1, 30, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(4, 1, 1, '售后服务', 0, ',4,', 1, 40, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(5, 1, 1, '诚聘英才', 0, ',5,', 1, 50, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(6, 1, 1, '合作洽谈', 0, ',6,', 1, 60, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(7, 1, 1, '关于我们', 0, ',7,', 1, 70, 'story', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(8, 1, 1, '首页', 0, ',8,', 1, 10, 'bug', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(9, 1, 1, '新闻中心', 0, ',9,', 1, 20, 'bug', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(10, 1, 1, '成果展示', 0, ',10,', 1, 30, 'bug', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(11, 1, 1, '售后服务', 0, ',11,', 1, 40, 'bug', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(12, 1, 1, '诚聘英才', 0, ',12,', 1, 50, 'bug', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(13, 1, 1, '合作洽谈', 0, ',13,', 1, 60, 'bug', ''); +INSERT INTO `zt_module` (`id`, `company`, `root`, `name`, `parent`, `path`, `grade`, `order`, `type`, `owner`) VALUES(14, 1, 1, '关于我们', 0, ',14,', 1, 70, 'bug', ''); + +INSERT INTO `zt_product` (`id`, `company`, `name`, `code`, `order`, `status`, `desc`, `PO`, `QM`, `RM`, `acl`, `whitelist`, `createdBy`, `createdDate`, `deleted`) VALUES(1, 1, '公司企业网站建设', 'companyWebsite', 0, 'normal', '建立公司企业网站,可以更好对外展示。
    ', 'productManager', 'testManager', 'productManager', 'open', '', 'productManager', '2012-06-05 09:57:07', '0'); +INSERT INTO `zt_product` (`id`, `company`, `name`, `code`, `order`, `status`, `desc`, `PO`, `QM`, `RM`, `acl`, `whitelist`, `createdBy`, `createdDate`, `deleted`) VALUES(2, 1, '企业内部工时管理系统', 'workhourManage', 0, 'normal', '', 'productManager', 'testManager', 'productManager', 'open', '', 'productManager', '2012-06-05 11:15:20', '0'); + +INSERT INTO `zt_productPlan` (`id`, `company`, `product`, `title`, `desc`, `begin`, `end`, `deleted`) VALUES(1, 1, 1, '1.0版本', '开发出企业网站1.0版本。
    ', '2000-01-01', '2015-01-01', '0'); + +INSERT INTO `zt_project` (`id`, `company`, `isCat`, `catID`, `type`, `parent`, `name`, `code`, `begin`, `end`, `days`, `status`, `statge`, `pri`, `desc`, `goal`, `openedBy`, `openedDate`, `closedBy`, `closedDate`, `canceledBy`, `canceledDate`, `PO`, `PM`, `QM`, `RM`, `team`, `acl`, `whitelist`, `order`, `deleted`) VALUES(1, 1, '0', 0, 'sprint', 0, '企业网站第一期', 'coWeb1', '2012-06-05', '2012-12-04', 184, 'done', '1', '1', '', '开发企业网站的基本雏形。
    ', '', 0, '', 0, '', 0, 'productManager', 'projectManager', 'testManager', 'productManager', '公司开发团队', 'open', '', 0, '0'); +INSERT INTO `zt_project` (`id`, `company`, `isCat`, `catID`, `type`, `parent`, `name`, `code`, `begin`, `end`, `days`, `status`, `statge`, `pri`, `desc`, `goal`, `openedBy`, `openedDate`, `closedBy`, `closedDate`, `canceledBy`, `canceledDate`, `PO`, `PM`, `QM`, `RM`, `team`, `acl`, `whitelist`, `order`, `deleted`) VALUES(2, 1, '0', 0, 'sprint', 0, '企业网站第二期', 'coWebsite2', '2013-06-05', '2014-06-04', 365, 'wait', '1', '1', '', '', '', 0, '', 0, '', 0, 'productManager', 'projectManager', 'testManager', 'productManager', '公司开发团队', 'open', '', 0, '0'); + +INSERT INTO `zt_projectProduct` (`company`, `project`, `product`) VALUES(1, 1, 1); +INSERT INTO `zt_projectProduct` (`company`, `project`, `product`) VALUES(1, 2, 1); + +INSERT INTO `zt_projectStory` (`company`, `project`, `product`, `story`, `version`) VALUES(1, 1, 1, 4, 1); +INSERT INTO `zt_projectStory` (`company`, `project`, `product`, `story`, `version`) VALUES(1, 1, 1, 3, 2); +INSERT INTO `zt_projectStory` (`company`, `project`, `product`, `story`, `version`) VALUES(1, 1, 1, 2, 1); +INSERT INTO `zt_projectStory` (`company`, `project`, `product`, `story`, `version`) VALUES(1, 1, 1, 1, 1); + + +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(1, 1, 1, 1, 1, 'po', 0, '首页设计和开发', '', '', 1, 1, 'active', 'developed', '', 'productManager', '2012-06-05 10:09:49', 'productManager', '0000-00-00 00:00:00', 'productManager', '2012-06-05 10:25:19', 'productManager, ', '2012-06-05', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 1, '0'); +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(2, 1, 1, 2, 1, 'po', 0, '新闻中心的设计和开发。', '', '', 1, 1, 'active', 'developed', '', 'productManager', '2012-06-05 10:16:37', 'productManager', '2012-06-05 10:16:37', 'productManager', '2012-06-05 10:25:33', 'productManager, ', '2012-06-05', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 1, '0'); +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(3, 1, 1, 3, 1, 'po', 0, '成果展示的设计和开发', '', '', 1, 0, 'active', 'developed', '', 'productManager', '2012-06-05 10:18:10', 'productManager', '2012-06-05 10:18:10', 'productManager', '2012-06-05 10:25:38', 'productManager, ', '2012-06-05', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 2, '0'); +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(4, 1, 1, 4, 1, 'po', 0, '售后服务的设计和开发', '', '', 1, 1, 'active', 'projected', '', 'productManager', '2012-06-05 10:20:16', 'productManager', '2012-06-05 10:20:16', 'productManager', '2012-06-05 10:25:42', 'productManager, ', '2012-06-05', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 1, '0'); +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(5, 1, 1, 5, 1, 'po', 0, '诚聘英才的设计和开发', '', '', 1, 1, 'draft', 'planned', '', 'productManager', '2012-06-05 10:21:39', 'productManager', '2012-06-05 10:21:39', '', '0000-00-00 00:00:00', '', '0000-00-00', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 1, '0'); +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(6, 1, 1, 6, 1, 'po', 0, '合作洽谈的设计和开发', '', '', 1, 1, 'draft', 'planned', '', 'productManager', '2012-06-05 10:23:11', 'productManager', '2012-06-05 10:23:11', '', '0000-00-00 00:00:00', '', '0000-00-00', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 1, '0'); +INSERT INTO `zt_story` (`id`, `company`, `product`, `module`, `plan`, `source`, `fromBug`, `title`, `keywords`, `type`, `pri`, `estimate`, `status`, `stage`, `mailto`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `lastEditedBy`, `lastEditedDate`, `reviewedBy`, `reviewedDate`, `closedBy`, `closedDate`, `closedReason`, `toBug`, `childStories`, `linkStories`, `duplicateStory`, `version`, `deleted`) VALUES(7, 1, 1, 7, 1, 'po', 0, '关于我们的设计和开发', '', '', 1, 1, 'draft', 'planned', '', 'productManager', '2012-06-05 10:24:19', 'productManager', '2012-06-05 10:24:19', '', '0000-00-00 00:00:00', '', '0000-00-00', '', '0000-00-00 00:00:00', '', 0, '', '', 0, 1, '0'); + +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 1, 1, '首页设计和开发', '作为一名本公司的用户,我希望可以在首页看到该公司网站的基本内容,例如最新动态、部分成果展示、联系信息、工商信息等。
    ', '开发并通过验收
    '); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 2, 1, '新闻中心的设计和开发。', '作为一名本公司的用户,我希望可以在新闻中心看到该公司网站的企业新闻,这样可以通过新闻了解企业的最新动态。
    ', ''); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 3, 1, '成果展示的设计和开发', ' 作为一名本公司的用户,我希望可以在成果展示看到该公司网站的企业新闻,这样可以方便我进行了解该公司的产品并进行购买。 
    ', ''); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 3, 2, '成果展示的设计和开发', ' 作为一名本公司的用户,我希望可以在成果展示看到该公司网站的吹产品,这样可以方便我进行了解该公司的产品并进行购买。 
    ', ''); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 4, 1, '售后服务的设计和开发', '作为一名本公司的用户,我希望可以在售后服务看到该公司网站的售后服务,这样可以方便我联系该公司来解决我的问题。 
    ', ''); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 5, 1, '诚聘英才的设计和开发', '作为一名求职者,我希望可以在诚聘英才里看到该公司的招聘信息,这样可以方便我应聘该公司。 
    ', ''); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 6, 1, '合作洽谈的设计和开发', '作为一名合作商,我希望可以在合作洽谈里看到该公司对外的合作内容,这样可以方便我和该公司进行合作洽谈。 
    ', ''); +INSERT INTO `zt_storySpec` (`company`, `story`, `version`, `title`, `spec`, `verify`) VALUES(1, 7, 1, '关于我们的设计和开发', '我希望可以在关于我们里看到该公司的基本信息,这样可以方便我了解该公司。
    ', ''); + +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(1, 1, 1, 1, 1, 0, '首页页面的设计', 'design', 1, 10, 7, 0, '0000-00-00', 'done', 12, '', '首页页面的设计
    ', 'projectManager', '2012-06-05 10:32:03', 'projectManager', '2012-06-05 10:41:20', 'dev1', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', '', 'dev1', '2012-06-05 10:41:20', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(2, 1, 1, 1, 1, 0, '首页的开发', 'devel', 1, 10, 8, 0, '0000-00-00', 'done', 1, '', '首页的开发
    ', 'projectManager', '2012-06-05 10:32:23', 'dev1', '2012-06-05 10:41:20', 'dev1', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', '', 'dev1', '2012-06-05 10:41:20', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(3, 1, 1, 2, 1, 0, '新闻中心的设计', 'design', 1, 8, 8, 0, '0000-00-00', 'done', 1, '', '新闻中心的设计
    ', 'projectManager', '2012-06-05 10:33:01', 'dev2', '2012-06-05 10:42:56', 'dev2', '2012-06-05 10:42:56', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', '', 'dev2', '2012-06-05 10:42:56', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(4, 1, 1, 2, 1, 0, '新闻中心的开发', 'devel', 1, 8, 5, 0, '0000-00-00', 'done', 1, '', '新闻中心的开发
    ', 'projectManager', '2012-06-05 10:33:21', 'dev2', '2012-06-05 10:42:56', 'dev2', '2012-06-05 10:42:56', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', '', 'dev2', '2012-06-05 10:42:56', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(5, 1, 1, 3, 2, 0, '成果展示的设计', 'design', 1, 8, 5, 0, '0000-00-00', 'done', 1, '', '成果展示的设计
    ', 'projectManager', '2012-06-05 10:33:44', 'dev3', '2012-06-05 10:43:32', 'dev3', '2012-06-05 10:43:32', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', '', 'dev3', '2012-06-05 10:43:32', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(6, 1, 1, 3, 2, 0, '成果展示的开发', 'devel', 1, 8, 5, 0, '0000-00-00', 'done', 1, '', '成果展示的开发
    ', 'projectManager', '2012-06-05 10:33:59', 'dev3', '2012-06-05 10:43:32', 'dev3', '2012-06-05 10:43:32', '', '0000-00-00 00:00:00', '', '0000-00-00 00:00:00', '', 'dev3', '2012-06-05 10:43:32', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(7, 1, 1, 4, 1, 0, '售后服务的设计', 'design', 1, 8, 0, 8, '0000-00-00', 'cancel', 1, '', '售后服务的设计
    ', 'projectManager', '2012-06-05 10:34:25', 'projectManager', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', 'dev1', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', '', 'dev1', '2012-06-05 10:41:20', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(8, 1, 1, 4, 1, 0, '售后服务的开发', 'devel', 1, 8, 6, 0, '0000-00-00', 'cancel', 12, '', '售后服务的开发
    ', 'projectManager', '2012-06-05 10:34:45', 'projectManager', '2012-06-05 10:41:20', 'dev1', '0000-00-00 00:00:00', 'dev1', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', '', 'dev1', '2012-06-05 10:41:20', '0'); +INSERT INTO `zt_task` (`id`, `company`, `project`, `story`, `storyVersion`, `fromBug`, `name`, `type`, `pri`, `estimate`, `consumed`, `left`, `deadline`, `status`, `statusCustom`, `mailto`, `desc`, `openedBy`, `openedDate`, `assignedTo`, `assignedDate`, `finishedBy`, `finishedDate`, `canceledBy`, `canceledDate`, `closedBy`, `closedDate`, `closedReason`, `lastEditedBy`, `lastEditedDate`, `deleted`) VALUES(9, 1, 1, 4, 1, 0, '售后服务的开发', 'devel', 1, 8, 0, 8, '0000-00-00', 'cancel', 1, '', '售后服务的开发
    ', 'projectManager', '2012-06-05 10:35:01', 'projectManager', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', 'dev1', '2012-06-05 10:41:20', '', '0000-00-00 00:00:00', '', 'dev1', '2012-06-05 10:41:20', '0'); + + +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 1, 'projectManager', '', '2012-06-05', 184, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 1, 'dev1', '', '2012-06-05', 184, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 1, 'dev2', '', '2012-06-05', 184, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 1, 'dev3', '', '2012-06-05', 184, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 2, 'projectManager', '', '2012-06-05', 365, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 2, 'dev1', '', '2012-06-05', 365, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 2, 'dev2', '', '2012-06-05', 365, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 2, 'dev3', '', '2012-06-05', 365, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 2, 'productManager', 'PO', '2012-06-05', 365, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 2, 'testManager', 'QM', '2012-06-05', 365, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 1, 'productManager', 'PO', '2012-06-05', 184, 7); +INSERT INTO `zt_team` (`company`, `project`, `account`, `role`, `join`, `days`, `hours`) VALUES(1, 1, 'testManager', 'QM', '2012-06-05', 184, 7); + +INSERT INTO `zt_testResult` (`id`, `company`, `run`, `case`, `version`, `caseResult`, `stepResults`, `lastRunner`, `date`) VALUES(1, 1, 4, 1, 1, 'pass', 'a:1:{i:1;a:2:{s:6:"result";s:4:"pass";s:4:"real";s:0:"";}}', 'testManager', '2012-06-05 11:11:00'); +INSERT INTO `zt_testResult` (`id`, `company`, `run`, `case`, `version`, `caseResult`, `stepResults`, `lastRunner`, `date`) VALUES(2, 1, 3, 2, 1, 'pass', 'a:1:{i:2;a:2:{s:6:"result";s:4:"pass";s:4:"real";s:0:"";}}', 'testManager', '2012-06-05 11:11:05'); +INSERT INTO `zt_testResult` (`id`, `company`, `run`, `case`, `version`, `caseResult`, `stepResults`, `lastRunner`, `date`) VALUES(3, 1, 2, 3, 1, 'pass', 'a:1:{i:3;a:2:{s:6:"result";s:4:"pass";s:4:"real";s:0:"";}}', 'testManager', '2012-06-05 11:11:07'); +INSERT INTO `zt_testResult` (`id`, `company`, `run`, `case`, `version`, `caseResult`, `stepResults`, `lastRunner`, `date`) VALUES(4, 1, 1, 4, 1, 'pass', 'a:1:{i:4;a:2:{s:6:"result";s:4:"pass";s:4:"real";s:0:"";}}', 'testManager', '2012-06-05 11:11:08'); + +INSERT INTO `zt_testRun` (`id`, `company`, `task`, `case`, `version`, `assignedTo`, `lastRunner`, `lastRunDate`, `lastRunResult`, `status`) VALUES(1, 1, 1, 4, 1, '', 'testManager', '2012-06-05 11:11:08', 'pass', 'done'); +INSERT INTO `zt_testRun` (`id`, `company`, `task`, `case`, `version`, `assignedTo`, `lastRunner`, `lastRunDate`, `lastRunResult`, `status`) VALUES(2, 1, 1, 3, 1, '', 'testManager', '2012-06-05 11:11:07', 'pass', 'done'); +INSERT INTO `zt_testRun` (`id`, `company`, `task`, `case`, `version`, `assignedTo`, `lastRunner`, `lastRunDate`, `lastRunResult`, `status`) VALUES(3, 1, 1, 2, 1, '', 'testManager', '2012-06-05 11:11:05', 'pass', 'done'); +INSERT INTO `zt_testRun` (`id`, `company`, `task`, `case`, `version`, `assignedTo`, `lastRunner`, `lastRunDate`, `lastRunResult`, `status`) VALUES(4, 1, 1, 1, 1, '', 'testManager', '2012-06-05 11:11:00', 'pass', 'done'); + +INSERT INTO `zt_testTask` (`id`, `company`, `name`, `product`, `project`, `build`, `owner`, `begin`, `end`, `desc`, `status`, `deleted`) VALUES(1, 1, '企业网站第一期测试任务', 1, 1, 'trunk', 'testManager', '2012-06-05', '2013-06-21', '', 'wait', '0'); + + +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(2, 1, 5, 'productManager', 'e10adc3949ba59abbe56e057f20f883e', '产品经理', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 3, '192.168.0.8', 1338866083, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(3, 1, 6, 'projectManager', 'e10adc3949ba59abbe56e057f20f883e', '项目经理', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 3, '192.168.0.8', 1338865876, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(4, 1, 2, 'dev1', 'e10adc3949ba59abbe56e057f20f883e', '开发甲', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 1, '192.168.0.8', 1338863860, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(5, 1, 2, 'dev2', 'e10adc3949ba59abbe56e057f20f883e', '开发乙', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 1, '192.168.0.8', 1338864116, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(6, 1, 2, 'dev3', 'e10adc3949ba59abbe56e057f20f883e', '开发丙', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 1, '192.168.0.8', 1338864187, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(7, 1, 3, 'tester1', 'e10adc3949ba59abbe56e057f20f883e', '测试甲', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 3, '192.168.0.8', 1338865739, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(8, 1, 3, 'tester2', 'e10adc3949ba59abbe56e057f20f883e', '测试乙', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 2, '192.168.0.8', 1338865450, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(9, 1, 3, 'tester3', 'e10adc3949ba59abbe56e057f20f883e', '测试丙', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 1, '192.168.0.8', 1338865125, '0'); +INSERT INTO `zt_user` (`id`, `company`, `dept`, `account`, `password`, `realname`, `nickname`, `commiter`, `avatar`, `birthday`, `gender`, `email`, `msn`, `qq`, `yahoo`, `gtalk`, `wangwang`, `mobile`, `phone`, `address`, `zipcode`, `join`, `visits`, `ip`, `last`, `deleted`) VALUES(10, 1, 1, 'testManager', 'e10adc3949ba59abbe56e057f20f883e', '测试经理', '', '', '', '0000-00-00', 'm', '', '', '', '', '', '', '', '', '', '', '0000-00-00', 6, '192.168.0.8', 1338865842, '0'); + +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'productManager', 2); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'projectManager', 5); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'dev1', 3); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'dev2', 3); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'dev3', 3); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'tester3', 4); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'tester2', 4); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'tester1', 4); +INSERT INTO `zt_userGroup` (`company`, `account`, `group`) VALUES(1, 'testManager', 4); diff --git a/trunk/db/update0.1.sql b/trunk/db/update0.1.sql new file mode 100644 index 0000000000..23556d395e --- /dev/null +++ b/trunk/db/update0.1.sql @@ -0,0 +1,152 @@ +-- story优先级的默认值。 +ALTER TABLE `zt_story` CHANGE `pri` `pri` TINYINT( 3 ) UNSIGNED NOT NULL DEFAULT '3' + +-- 修改project code字段的长度。 +ALTER TABLE `zt_project` CHANGE `code` `code` VARCHAR( 20 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL + +-- task 暂时增加left字段: + ALTER TABLE `zt_task` ADD `left` tinyINT(3) NOT NULL AFTER `consumed` + +-- 修改日期字段 +ALTER TABLE `zt_bug` CHANGE `openedDate` `openedDate` DATETIME NOT NULL , +CHANGE `assignedDate` `assignedDate` DATETIME NOT NULL , +CHANGE `resolvedDate` `resolvedDate` DATETIME NOT NULL , +CHANGE `closedDate` `closedDate` DATETIME NOT NULL , +CHANGE `lastEditedDate` `lastEditedDate` DATETIME NOT NULL + +RENAME TABLE `zentao`.`zt_division` TO `zentao`.`zt_dept` ; +ALTER TABLE `zt_user` CHANGE `division` `dept` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0' + + +-- 0.2版本: +-- +-- 修改task表name字段的长度。 +-- 修改task表的时间的类型,可以是浮点数。 +ALTER TABLE `zt_task` CHANGE `estimate` `estimate` FLOAT UNSIGNED NOT NULL , +CHANGE `consumed` `consumed` FLOAT UNSIGNED NOT NULL , +CHANGE `left` `left` FLOAT UNSIGNED NOT NULL + +-- todo表 + +CREATE TABLE IF NOT EXISTS `zt_todo` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `account` char(30) NOT NULL, + `date` date NOT NULL default '0000-00-00', + `begin` smallint(4) unsigned zerofill NOT NULL, + `end` smallint(4) unsigned zerofill NOT NULL, + `type` char(10) NOT NULL, + `idvalue` mediumint(8) unsigned NOT NULL default '0', + `pri` tinyint(3) unsigned NOT NULL, + `name` char(90) NOT NULL, + `desc` char(255) NOT NULL default '', + `status` enum('wait','doing','done') NOT NULL default 'wait', + PRIMARY KEY (`id`), + KEY `user` (`account`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- 更新product, project表中的company字段: +update zt_product set company = 1; +update zt_project set company = 1; + + -- 更新story字段里面的estimate字段: +ALTER TABLE `zt_story` CHANGE `estimate` `estimate` FLOAT UNSIGNED NOT NULL + +-- 还是使用datetime字段。 +ALTER TABLE `zt_story` CHANGE `openedDate` `openedDate` DATETIME NOT NULL , +CHANGE `assignedDate` `assignedDate` DATETIME NOT NULL , +CHANGE `lastEditedDate` `lastEditedDate` DATETIME NOT NULL , +CHANGE `closedDate` `closedDate` DATETIME NOT NULL + +-- 增加diff字段。 +ALTER TABLE `zt_history` ADD `diff` TEXT NOT NULL + +-- 10.27 +ALTER TABLE `zt_todo` CHANGE `desc` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL +ALTER TABLE `zt_task` CHANGE `name` `name` VARCHAR( 90 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL + +ALTER TABLE `zt_task` CHANGE `estimate` `estimate` FLOAT UNSIGNED NOT NULL , +CHANGE `consumed` `consumed` FLOAT UNSIGNED NOT NULL , +CHANGE `left` `left` FLOAT UNSIGNED NOT NULL + + +-- 11.2 todo表增加private字段: +ALTER TABLE `zt_todo` ADD `private` BOOL NOT NULL + +-- 11.4 增加消耗表。 +CREATE TABLE IF NOT EXISTS `zt_burn` ( + `project` mediumint(8) unsigned NOT NULL, + `date` date NOT NULL, + `left` float NOT NULL, + `consumed` float NOT NULL, + PRIMARY KEY (`project`,`date`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- 11.5 project status字段更改。 +ALTER TABLE `zt_project` CHANGE `status` `status` VARCHAR( 10 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL + +-- 11.10. + +ALTER TABLE `zt_bug` CHANGE `openedDate` `openedDate` DATETIME NOT NULL , +CHANGE `assignedDate` `assignedDate` DATETIME NOT NULL , +CHANGE `resolvedDate` `resolvedDate` DATETIME NOT NULL , +CHANGE `closedDate` `closedDate` DATETIME NOT NULL , +CHANGE `lastEditedDate` `lastEditedDate` DATETIME NOT NULL + +-- 11.12 + +ALTER TABLE `zt_bug` ADD `duplicateBug` MEDIUMINT UNSIGNED NOT NULL AFTER `closedDate` , +ADD `linkBug` VARCHAR( 255 ) NOT NULL AFTER `duplicateBug` , +ADD `case` MEDIUMINT UNSIGNED NOT NULL AFTER `linkBug` , +ADD `result` MEDIUMINT UNSIGNED NOT NULL AFTER `case` + +-- 11.13 +ALTER TABLE `zt_case` CHANGE `openedDate` `openedDate` DATETIME NOT NULL , +CHANGE `lastEditedDate` `lastEditedDate` DATETIME NOT NULL + +-- 11.16 +ALTER TABLE `zt_file` CHANGE `addedDate` `addedDate` DATETIME NOT NULL; +ALTER TABLE `zt_file` ADD `title` CHAR( 90 ) NOT NULL AFTER `file`; +ALTER TABLE `zt_file` ADD `objectType` CHAR( 10 ) NOT NULL AFTER `size` , +ADD `objectID` MEDIUMINT NOT NULL AFTER `objectType` ; + +ALTER TABLE `zt_file` CHANGE `file` `pathname` CHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_file` CHANGE `type` `extension` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + +ALTER TABLE `zt_task` CHANGE `status` `status` ENUM( 'wait', 'doing', 'done', 'cancel' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'wait' + +ALTER TABLE `zt_todo` CHANGE `name` `name` CHAR( 150 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL + + +-- 12.10 新增productPlan表。 +CREATE TABLE `zentao`.`zt_productPlan` ( + `id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , + `product` MEDIUMINT UNSIGNED NOT NULL , + `title` VARCHAR( 90 ) NOT NULL , + `desc` VARCHAR( 255 ) NOT NULL , + `begin` DATE NOT NULL , + `end` DATE NOT NULL +) ENGINE = MYISAM ; + +-- 将zt_story中的release改为plan。 +ALTER TABLE `zt_story` CHANGE `replease` `plan` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0' + +-- 12.11 修改case表。 + +ALTER TABLE `zt_case` CHANGE `type` `type` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '1', +CHANGE `status` `status` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '1', +CHANGE `openedDate` `openedDate` DATETIME NOT NULL , +CHANGE `lastEditedDate` `lastEditedDate` DATETIME NOT NULL + +ALTER TABLE `zt_case` CHANGE `title` `title` CHAR( 90 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL + +-- 12.28 zt_task字段增加自定义排序字段。 +ALTER TABLE `zt_task` ADD `statusCustom` TINYINT UNSIGNED NOT NULL AFTER `status` , +ADD INDEX ( statusCustom ); +update zt_task set statusCustom = locate(status, 'wait,doing,done,cancel') + +-- 增加类型字段。-- +ALTER TABLE `zt_task` ADD `type` VARCHAR( 20 ) NOT NULL AFTER `name` , +ADD INDEX ( TYPE ) + +-- todo 增加状态-- +ALTER TABLE `zt_todo` CHANGE `status` `status` CHAR( 20 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'wait' diff --git a/trunk/db/update0.3.sql b/trunk/db/update0.3.sql new file mode 100644 index 0000000000..3e34b220ea --- /dev/null +++ b/trunk/db/update0.3.sql @@ -0,0 +1,35 @@ +-- 20100114: 重新修改build的结构。 +DROP TABLE IF EXISTS `zt_build`; +CREATE TABLE IF NOT EXISTS `zt_build` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `product` mediumint(8) unsigned NOT NULL default '0', + `project` mediumint(8) unsigned NOT NULL default '0', + `name` char(30) NOT NULL default '', + `scmPath` char(255) NOT NULL, + `filePath` char(255) NOT NULL, + `date` date NOT NULL, + `builder` char(30) NOT NULL default '', + `desc` char(255) NOT NULL default '', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- 20100115: 重新修改release的结构。 +DROP TABLE IF EXISTS `zt_release`; +CREATE TABLE IF NOT EXISTS `zt_release` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `product` mediumint(8) unsigned NOT NULL default '0', + `build` mediumint(8) unsigned NOT NULL, + `name` char(30) NOT NULL default '', + `date` date NOT NULL, + `desc` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- 20100115: fix bug 14 +ALTER TABLE `zt_productPlan` CHANGE `title` `title` VARCHAR( 90 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , +CHANGE `desc` `desc` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + +-- 20100125 user表增加status字段。 +ALTER TABLE `zt_user` ADD `status` VARCHAR( 30 ) NOT NULL DEFAULT 'active', +ADD INDEX ( STATUS ) diff --git a/trunk/db/update0.4.sql b/trunk/db/update0.4.sql new file mode 100644 index 0000000000..ba344d1287 --- /dev/null +++ b/trunk/db/update0.4.sql @@ -0,0 +1,114 @@ +-- 20100128 修改user表中ip字段的默认值,解决install失败的问题。 +ALTER TABLE `zt_user` CHANGE `ip` `ip` CHAR( 15 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; + +-- 20100128: 调整casestep表。 +ALTER TABLE `zt_caseStep` CHANGE `caseVersion` `version` SMALLINT( 3 ) UNSIGNED NOT NULL DEFAULT '0'; +ALTER TABLE `zt_caseStep` CHANGE `step` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; +ALTER TABLE `zt_caseStep` CHANGE `expect` `expect` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; +ALTER TABLE `zt_caseStep` ADD INDEX ( `case` , `version` ) ; + +-- 20100128 转换case中的step字段 +update zt_case set version = 1 where version = 0; +insert into zt_caseStep select '', id,version,steps, '' from zt_case; +ALTER TABLE `zt_case` DROP `steps`; + +DROP TABLE `zt_testPlan`; +CREATE TABLE IF NOT EXISTS `zt_testTask` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `name` char(90) NOT NULL, + `product` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL default '0', + `build` char(30) NOT NULL, + `begin` date NOT NULL, + `end` date NOT NULL, + `desc` text NOT NULL, + `status` char(30) NOT NULL, + PRIMARY KEY (`id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE `zt_planCase`; +CREATE TABLE IF NOT EXISTS `zt_testRun` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `task` mediumint(8) unsigned NOT NULL default '0', + `case` mediumint(8) unsigned NOT NULL default '0', + `version` tinyint(3) unsigned NOT NULL default '0', + `assignedTo` char(30) NOT NULL default '', + `lastRun` datetime NOT NULL, + `lastResult` char(30) NOT NULL, + `status` char(30) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `task` (`task`,`case`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +DROP TABLE `zt_caseResult`; +DROP TABLE `zt_resultStep`; +CREATE TABLE IF NOT EXISTS `zt_testResult` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `run` mediumint(8) unsigned NOT NULL, + `case` mediumint(8) unsigned NOT NULL, + `version` smallint(5) unsigned NOT NULL, + `caseResult` char(30) NOT NULL, + `stepResults` text NOT NULL, + `date` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `run` (`run`), + KEY `case` (`case`,`version`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + +-- 20100204 adjust the story. +ALTER TABLE `zt_story` DROP `attatchment`; +ALTER TABLE `zt_story` CHANGE `version` `version` SMALLINT NOT NULL DEFAULT '1'; +ALTER TABLE `zt_story` ADD `closedReason` VARCHAR( 30 ) NOT NULL AFTER `closedDate`; +ALTER TABLE `zt_story` ADD `stage` VARCHAR( 30 ) NOT NULL AFTER `status`; +ALTER TABLE `zt_story` ADD `reviewedBy` VARCHAR( 30 ) NOT NULL AFTER `lastEditedDate`; +ALTER TABLE `zt_story` ADD `reviewedDate` DATETIME NOT NULL AFTER `reviewedBy`; +UPDATE zt_story SET version = 1 WHERE version = 0; +UPDATE zt_story SET status = 'closed', closedReason = 'done', stage='released', closedBy = lastEditedBy, closedDate = lastEditedDate, assignedTo = 'closed' WHERE status = 'done'; +UPDATE zt_story SET status = 'draft' WHERE status = 'wait'; +UPDATE zt_story SET status = 'active' WHERE status = 'doing'; +ALTER TABLE `zt_story` CHANGE `bug` `fromBug` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0'; +ALTER TABLE `zt_story` ADD `toBug` MEDIUMINT NOT NULL AFTER `closedReason`; +ALTER TABLE `zt_story` ADD `childStories` VARCHAR( 255 ) NOT NULL AFTER `toBug` , +ADD `linkStories` VARCHAR( 255 ) NOT NULL AFTER `childStories`; +ALTER TABLE `zt_story` ADD `duplicateStory` MEDIUMINT UNSIGNED NOT NULL AFTER `linkStories`; + +CREATE TABLE IF NOT EXISTS `zt_storySpec` ( + `story` mediumint(9) NOT NULL, + `version` smallint(6) NOT NULL, + `title` VARCHAR( 90 ) NOT NULL, + `spec` text NOT NULL, + UNIQUE KEY `story` (`story`,`version`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +INSERT INTO zt_storySpec select id,version,title,spec FROM zt_story; +ALTER TABLE `zt_story` DROP `spec`; +ALTER TABLE `zt_file` ADD `extra` VARCHAR( 255 ) NOT NULL ; +update `zt_file` set extra = 1 where objectType = 'story'; + +ALTER TABLE `zt_bug` ADD `storyVersion` SMALLINT NOT NULL DEFAULT '1' AFTER `story`; +ALTER TABLE `zt_bug` ADD `caseVersion` SMALLINT NOT NULL DEFAULT '1' AFTER `case`; +ALTER TABLE `zt_bug` DROP `field1` , +DROP `field2` , +DROP `feild3` ; + +ALTER TABLE `zt_case` DROP `field1` , +DROP `field2` , +DROP `feidl3` ; +ALTER TABLE `zt_case` ADD `storyVersion` SMALLINT NOT NULL DEFAULT '1' AFTER `story`; +ALTER TABLE `zt_projectStory` ADD `version` SMALLINT NOT NULL DEFAULT '1'; +ALTER TABLE `zt_task` ADD `storyVersion` SMALLINT NOT NULL DEFAULT '1' AFTER `story`; + +-- delete releation. +DROP TABLE `zt_releation`; + +-- 20100208 adjust action table. +ALTER TABLE `zt_action` ADD `extra` VARCHAR( 255 ) NOT NULL; +UPDATE zt_action SET extra = substr( ACTION , 13 ) , ACTION = 'Resolved' WHERE ACTION LIKE 'Resolved%'; + +--20100210 adjust the reviewedDate +ALTER TABLE `zt_story` CHANGE `reviewedDate` `reviewedDate` DATE NOT NULL; + +--20100220 action: convert the date from timestamp to datetime +ALTER TABLE `zt_action` ADD `datetmp` VARCHAR( 255 ) NOT NULL AFTER `date`; +UPDATE zt_action SET datetmp = FROM_UNIXTIME( date ) ; +ALTER TABLE `zt_action` DROP `date`; +ALTER TABLE `zt_action` CHANGE `datetmp` `date` DATETIME NOT NULL; diff --git a/trunk/db/update0.5.sql b/trunk/db/update0.5.sql new file mode 100644 index 0000000000..bf097cb097 --- /dev/null +++ b/trunk/db/update0.5.sql @@ -0,0 +1,2 @@ +ALTER TABLE `zt_task` ADD `deadline` DATE NOT NULL AFTER `left` ; +ALTER TABLE `zt_bug` CHANGE `openedBuild` `openedBuild` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL diff --git a/trunk/db/update0.6.sql b/trunk/db/update0.6.sql new file mode 100644 index 0000000000..c79460bcd8 --- /dev/null +++ b/trunk/db/update0.6.sql @@ -0,0 +1,27 @@ +-- 2010-03-20 adjust bug table. +ALTER TABLE `zt_bug` ADD `pri` TINYINT UNSIGNED NOT NULL AFTER `severity` ; +ALTER TABLE `zt_bug` ADD `keywords` VARCHAR( 255 ) NOT NULL AFTER `title` ; +ALTER TABLE `zt_case` ADD `keywords` VARCHAR( 255 ) NOT NULL AFTER `title` ; +ALTER TABLE `zt_case` ADD `stage` VARCHAR( 255 ) NOT NULL AFTER `type` ; +ALTER TABLE `zt_case` ADD `howRun` VARCHAR( 30 ) NOT NULL AFTER `stage` , +ADD `scriptedBy` VARCHAR( 30 ) NOT NULL AFTER `howRun` , +ADD `scriptedDate` DATE NOT NULL AFTER `scriptedBy` , +ADD `scriptStatus` VARCHAR( 30 ) NOT NULL AFTER `scriptedDate` , +ADD `scriptLocation` VARCHAR( 255 ) NOT NULL AFTER `scriptStatus`, +ADD `linkCase` VARCHAR( 255 ) NOT NULL AFTER `version` ; + +-- 2010-03-27 fix bug 43 +update zt_projectStory,zt_story set zt_projectStory.product=zt_story.product where zt_projectStory.story=zt_story.id; + +-- 2010-03-27 add keywords to story. +ALTER TABLE `zt_story` ADD `keywords` VARCHAR( 255 ) NOT NULL AFTER `title` ; + +-- 2010-03-29 add fields of project. +ALTER TABLE `zt_project` ADD `acl` ENUM( 'open', 'private', 'custom' ) NOT NULL DEFAULT 'open', +ADD `whitelist` VARCHAR( 255 ) NOT NULL ; + +-- 2010-03-29 adjust the field length. +ALTER TABLE `zt_project` CHANGE `name` `name` VARCHAR( 90 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , +CHANGE `code` `code` VARCHAR( 45 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_product` CHANGE `name` `name` VARCHAR( 90 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , +CHANGE `code` `code` VARCHAR( 45 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL diff --git a/trunk/db/update1.0.1.sql b/trunk/db/update1.0.1.sql new file mode 100644 index 0000000000..bb0f75b302 --- /dev/null +++ b/trunk/db/update1.0.1.sql @@ -0,0 +1,17 @@ +-- 2010-07-01 task table. +ALTER TABLE `zt_task` ADD `mailto` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' AFTER `statusCustom`; + +--2010-07-05 userquery table. +CREATE TABLE IF NOT EXISTS `zt_userQuery` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `account` char(30) NOT NULL, + `module` varchar(30) NOT NULL, + `title` varchar(90) NOT NULL, + `form` text NOT NULL, + `sql` text NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`), + KEY `account` (`account`), + KEY `module` (`module`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/trunk/db/update1.0.beta.sql b/trunk/db/update1.0.beta.sql new file mode 100644 index 0000000000..347c76b928 --- /dev/null +++ b/trunk/db/update1.0.beta.sql @@ -0,0 +1,68 @@ +-- 2010-04-06 bug table. +ALTER TABLE `zt_bug` ADD `deleted` ENUM( "0", "1" ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_build` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_case` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_company` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_file` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_product` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_productPlan` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_project` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_release` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_story` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_task` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_testTask` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_user` ADD `deleted` ENUM( '0', '1' ) NOT NULL DEFAULT '0'; + +-- user table. +UPDATE zt_user SET deleted = '1' WHERE STATUS = 'delete'; +ALTER TABLE `zt_user` DROP `status`; +INSERT INTO zt_action(`company`, `objecttype`, `objectID`, `actor`, action, `date`, `comment` , `extra`) SELECT `company`, 'user', `id`, 'system', 'deleted', now( ) , '', 1 FROM zt_user WHERE zt_user.deleted ='1'; + +-- company fields. +ALTER TABLE `zt_action` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_bug` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_bug` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_build` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_build` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_burn` ADD `company` MEDIUMINT UNSIGNED NOT NULL FIRST ; +ALTER TABLE `zt_burn` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_case` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_case` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_caseStep` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_caseStep` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_effort` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_effort` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_groupPriv` ADD `company` MEDIUMINT NOT NULL FIRST ; +ALTER TABLE `zt_groupPriv` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_history` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_history` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_module` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_module` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_productPlan` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_productPlan` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_projectProduct` ADD `company` MEDIUMINT UNSIGNED NOT NULL FIRST ; +ALTER TABLE `zt_projectProduct` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_projectStory` ADD `company` MEDIUMINT NOT NULL FIRST ; +ALTER TABLE `zt_projectStory` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_release` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_release` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_story` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_story` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_storySpec` ADD `company` MEDIUMINT UNSIGNED NOT NULL FIRST ; +ALTER TABLE `zt_storySpec` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_task` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_task` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_taskEstimate` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_taskEstimate` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_team` ADD `company` MEDIUMINT UNSIGNED NOT NULL FIRST ; +ALTER TABLE `zt_team` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_testResult` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_testResult` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_testRun` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_testRun` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_testTask` ADD `company` MEDIUMINT UNSIGNED NOT NULL AFTER `id` ; +ALTER TABLE `zt_testTask` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_todo` ADD `company` MEDIUMINT NOT NULL AFTER `id` ; +ALTER TABLE `zt_todo` ADD INDEX ( `company` ) ; +ALTER TABLE `zt_userGroup` ADD `company` MEDIUMINT UNSIGNED NOT NULL FIRST ; +ALTER TABLE `zt_userGroup` ADD INDEX ( `company` ) ; diff --git a/trunk/db/update1.0.rc1.sql b/trunk/db/update1.0.rc1.sql new file mode 100644 index 0000000000..ebe161effb --- /dev/null +++ b/trunk/db/update1.0.rc1.sql @@ -0,0 +1,2 @@ +-- 2010-04-24 product status field. +UPDATE zt_product SET status='normal'; diff --git a/trunk/db/update1.1.sql b/trunk/db/update1.1.sql new file mode 100644 index 0000000000..e99ce9677b --- /dev/null +++ b/trunk/db/update1.1.sql @@ -0,0 +1,55 @@ +-- doc +CREATE TABLE IF NOT EXISTS `zt_doc` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` smallint(5) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL, + `lib` varchar(30) NOT NULL, + `module` varchar(30) NOT NULL, + `title` varchar(120) NOT NULL, + `digest` varchar(255) NOT NULL, + `type` varchar(30) NOT NULL, + `content` text NOT NULL, + `url` varchar(255) NOT NULL, + `views` smallint(5) unsigned NOT NULL, + `addedBy` varchar(30) NOT NULL, + `addedDate` datetime NOT NULL, + `editedBy` varchar(30) NOT NULL, + `editedDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- doc lib +CREATE TABLE IF NOT EXISTS `zt_docLib` ( + `id` smallint(5) unsigned NOT NULL auto_increment, + `company` smallint(5) unsigned NOT NULL, + `name` varchar(60) NOT NULL, + `deleted` enum('0','1') NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `company` (`company`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- module +ALTER TABLE `zt_module` CHANGE `product` `root` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0'; +ALTER TABLE `zt_module` CHANGE `view` `type` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_module` ADD `owner` VARCHAR( 30 ) NOT NULL ; +update zt_module set `type` = 'story' where `type` = 'product'; +-- tpl +CREATE TABLE IF NOT EXISTS `zt_userTPL` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `company` mediumint(8) unsigned NOT NULL, + `account` char(30) NOT NULL, + `type` char(30) NOT NULL, + `title` varchar(150) NOT NULL, + `content` text NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`), + KEY `account` (`account`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- product acl +ALTER TABLE `zt_product` ADD `acl` ENUM( 'open', 'private', 'custom' ) NOT NULL DEFAULT 'open' AFTER `desc` , +ADD `whitelist` VARCHAR( 255 ) NOT NULL AFTER `acl` ; + +-- product owner. +ALTER TABLE `zt_product` ADD `productOwner` VARCHAR( 30 ) NOT NULL AFTER `desc` , +ADD `bugOwner` VARCHAR( 30 ) NOT NULL AFTER `productOwner` ; diff --git a/trunk/db/update1.2.sql b/trunk/db/update1.2.sql new file mode 100644 index 0000000000..bff2d93507 --- /dev/null +++ b/trunk/db/update1.2.sql @@ -0,0 +1,3 @@ +-- 2010-09-12 doc table. +UPDATE `zt_doc` SET `type` = 'file'; +ALTER TABLE `zt_doc` ADD `keywords` VARCHAR( 255 ) NOT NULL AFTER `digest`; diff --git a/trunk/db/update1.3.sql b/trunk/db/update1.3.sql new file mode 100644 index 0000000000..8234771c40 --- /dev/null +++ b/trunk/db/update1.3.sql @@ -0,0 +1,42 @@ +-- 2010-11-13 project table. +ALTER TABLE `zt_project` ADD `RM` varchar(30) NOT NULL DEFAULT '' AFTER `QM`; +ALTER TABLE `zt_user` DROP `birthyear`; + +-- 2010-11-24 product table. +ALTER TABLE `zt_product` CHANGE `productOwner` `PO` VARCHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , +CHANGE `bugOwner` `QM` VARCHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_product` ADD `RM` VARCHAR( 30 ) NOT NULL AFTER `QM` ; +ALTER TABLE `zt_product` ADD `createdBy` VARCHAR( 30 ) NOT NULL AFTER `whitelist` , +ADD `createdDate` DATETIME NOT NULL AFTER `createdBy` ; + +-- fileds length. +ALTER TABLE `zt_bug` CHANGE `title` `title` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_build` CHANGE `name` `name` CHAR( 150 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_case` CHANGE `title` `title` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_dept` CHANGE `name` `name` CHAR( 60 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_doc` CHANGE `title` `title` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_story` CHANGE `title` `title` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_task` CHANGE `name` `name` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + +-- task table. +ALTER TABLE `zt_task` ADD `openedBy` VARCHAR( 30 ) NOT NULL AFTER `desc` , +ADD `openedDate` DATETIME NOT NULL AFTER `openedBy` , +ADD `assignedTo` VARCHAR( 30 ) NOT NULL AFTER `openedDate` , +ADD `assignedDate` DATETIME NOT NULL AFTER `assignedTo` , +ADD `finishedBy` VARCHAR( 30 ) NOT NULL AFTER `assignedDate` , +ADD `finishedDate` DATETIME NOT NULL AFTER `finishedBy` , +ADD `canceledBy` VARCHAR( 30 ) NOT NULL AFTER `finishedDate` , +ADD `canceledDate` DATETIME NOT NULL AFTER `canceledBy` , +ADD `closedBy` VARCHAR( 30 ) NOT NULL AFTER `canceledDate` , +ADD `closedDate` DATETIME NOT NULL AFTER `closedBy` ; +ALTER TABLE `zt_task` ADD `lastEditedBy` VARCHAR( 30 ) NOT NULL AFTER `closedDate` , +ADD `lastEditedDate` DATETIME NOT NULL AFTER `lastEditedBy` ; +UPDATE zt_task SET assignedTo = owner ; +ALTER TABLE `zt_task` DROP `owner`; +ALTER TABLE `zt_task` CHANGE `status` `status` ENUM( 'wait', 'doing', 'done', 'cancel', 'closed' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'wait'; +ALTER TABLE `zt_task` ADD `closedReason` VARCHAR( 30 ) NOT NULL AFTER `closedDate` ; + +update zt_groupPriv set method='finish' where module='task' and method='complete'; + +-- test task table. +ALTER TABLE `zt_testTask` ADD `owner` VARCHAR( 30 ) NOT NULL AFTER `build` ; diff --git a/trunk/db/update1.4.sql b/trunk/db/update1.4.sql new file mode 100644 index 0000000000..67ed3c36d5 --- /dev/null +++ b/trunk/db/update1.4.sql @@ -0,0 +1,4 @@ +-- 2011-2-16 change the length of the desc field. +ALTER TABLE `zt_build` CHANGE `desc` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +-- 2011-3-4 fix the error of bug's browse: +UPDATE zt_bug set browser='firefox3' where browser='firefx3'; diff --git a/trunk/db/update1.5.sql b/trunk/db/update1.5.sql new file mode 100644 index 0000000000..be265206a0 --- /dev/null +++ b/trunk/db/update1.5.sql @@ -0,0 +1,23 @@ +ALTER TABLE `zt_storySpec` ADD `verify` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + +CREATE TABLE IF NOT EXISTS `zt_extension` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `name` varchar(150) NOT NULL, + `code` varchar(30) NOT NULL, + `version` varchar(50) NOT NULL, + `author` varchar(100) NOT NULL, + `desc` text NOT NULL, + `license` text NOT NULL, + `site` varchar(150) NOT NULL, + `zentaoVersion` varchar(100) NOT NULL, + `installedTime` datetime NOT NULL, + `dirs` text NOT NULL, + `files` text NOT NULL, + `status` varchar(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `code` (`code`), + KEY `name` (`name`), + KEY `addedTime` (`installedTime`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/trunk/db/update2.0.sql b/trunk/db/update2.0.sql new file mode 100644 index 0000000000..ba7048c2d5 --- /dev/null +++ b/trunk/db/update2.0.sql @@ -0,0 +1,69 @@ +-- 2011-06-13, fix the error of testTask and testCase. +UPDATE `zt_groupPriv` SET `method` = 'testTask' WHERE `method`='testtask'; +UPDATE `zt_groupPriv` SET `method` = 'testCase' WHERE `method`='testcase'; + +-- 2011-06-30, add product and project field to action. +ALTER TABLE `zt_action` ADD `product` MEDIUMINT NOT NULL AFTER `objectID` , +ADD `project` MEDIUMINT NOT NULL AFTER `product` ; + +UPDATE zt_action, zt_story SET + zt_action.product = zt_story.product WHERE + zt_action.objectID = zt_story.id AND + zt_action.objectType = 'story'; + +UPDATE zt_action, zt_projectStory SET + zt_action.project = zt_projectStory.project WHERE + zt_action.objectID = zt_projectStory.story AND + zt_action.objectType = 'story'; + +UPDATE zt_action, zt_bug SET + zt_action.product = zt_bug.product, + zt_action.project = zt_bug.project WHERE + zt_action.objectID = zt_bug.id AND + zt_action.objectType = 'bug'; + +UPDATE zt_action, zt_build SET + zt_action.product = zt_build.product, + zt_action.project = zt_build.project WHERE + zt_action.objectID = zt_build.id AND + zt_action.objectType = 'build'; + +UPDATE zt_action, zt_case SET + zt_action.product = zt_case.product WHERE + zt_action.objectID = zt_case.id AND + zt_action.objectType = 'case'; + +UPDATE zt_action, zt_doc SET + zt_action.product = zt_doc.product, + zt_action.project = zt_doc.project WHERE + zt_action.objectID = zt_doc.id AND + zt_action.objectType = 'doc'; + +UPDATE zt_action SET product = objectID WHERE objectType = 'product'; +UPDATE zt_action SET project = objectID WHERE objectType = 'project'; + +UPDATE zt_action, zt_productPlan SET + zt_action.product = zt_productPlan.product WHERE + zt_action.objectID = zt_productPlan.id AND + zt_action.objectType = 'productplan'; + +UPDATE zt_action, zt_release, zt_build SET + zt_action.product = zt_release.product, + zt_action.project = zt_build.project WHERE + zt_action.objectID = zt_release.id AND + zt_release.build = zt_build.id AND + zt_action.objectType = 'release'; + +UPDATE zt_action, zt_task SET + zt_action.project = zt_task.project WHERE + zt_action.objectID = zt_task.id AND + zt_action.objectType = 'task'; + +UPDATE zt_action, zt_testTask SET + zt_action.project = zt_testTask.project, + zt_action.product = zt_testTask.product WHERE + zt_action.objectID = zt_testTask.id AND + zt_action.objectType = 'testtask'; + +-- 2011-07-04 add type field to extension. +ALTER TABLE `zt_extension` ADD `type` VARCHAR( 20 ) NOT NULL DEFAULT 'extension' AFTER `license` ; diff --git a/trunk/db/update2.1.sql b/trunk/db/update2.1.sql new file mode 100644 index 0000000000..4a35db775b --- /dev/null +++ b/trunk/db/update2.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE `zt_build` CHANGE `desc` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_productPlan` CHANGE `desc` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + + -- 2011-9-17 change length of name field in zt_module +ALTER TABLE `zt_module` CHANGE `name` `name` CHAR( 60 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; diff --git a/trunk/db/update2.2.sql b/trunk/db/update2.2.sql new file mode 100644 index 0000000000..8251bdf8cf --- /dev/null +++ b/trunk/db/update2.2.sql @@ -0,0 +1,22 @@ +ALTER TABLE `zt_build` CHANGE `desc` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_productPlan` CHANGE `desc` `desc` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + + -- 2011-9-17 change length of name field in zt_module +ALTER TABLE `zt_module` CHANGE `name` `name` CHAR( 60 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; + +ALTER TABLE `zt_story` ADD `source` VARCHAR( 20 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER `plan` ; +ALTER TABLE `zt_bug` ADD `confirmed` BOOL NOT NULL DEFAULT '0' AFTER `status` ; +ALTER TABLE `zt_bug` ADD `activatedCount` SMALLINT( 6 ) NOT NULL AFTER `confirmed` ; +UPDATE `zt_bug` SET `confirmed` = 1 WHERE status in('closed', 'resolved'); + + -- 2011-10-16 add precondition field in zt_case +ALTER TABLE `zt_case` ADD `precondition` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER `title`; + + -- 2011-10-22 add lastRun and lastResult field in zt_case +ALTER TABLE `zt_case` ADD `lastRun` DATETIME NOT NULL; +ALTER TABLE `zt_case` ADD `lastResult` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + +ALTER TABLE `zt_bug` ADD `toTask` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `task` ; +ALTER TABLE `zt_task` ADD `fromBug` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `storyVersion` ; + +ALTER TABLE `zt_case` ADD `fromBug` MEDIUMINT UNSIGNED NOT NULL AFTER `linkCase`; diff --git a/trunk/db/update2.3.sql b/trunk/db/update2.3.sql new file mode 100644 index 0000000000..d59bf5c4dd --- /dev/null +++ b/trunk/db/update2.3.sql @@ -0,0 +1,17 @@ +ALTER TABLE `zt_bug` ADD `toStory` MEDIUMINT( 8 ) NOT NULL DEFAULT '0' AFTER `toTask` ; +ALTER TABLE `zt_testResult` ADD `lastRunner` VARCHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER `stepResults` ; +ALTER TABLE `zt_testRun` ADD `lastRunner` VARCHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER `assignedTo` ; +ALTER TABLE `zt_case` ADD `lastRunner` VARCHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER `deleted` ; + +-- adjust the working hours. +ALTER TABLE `zt_project` ADD `days` SMALLINT UNSIGNED NOT NULL AFTER `end`; +ALTER TABLE `zt_team` CHANGE `joinDate` `join` DATE NOT NULL DEFAULT '0000-00-00'; +ALTER TABLE `zt_team` CHANGE `workingHour` `hours` TINYINT( 3 ) UNSIGNED NOT NULL DEFAULT '0'; +ALTER TABLE `zt_team` ADD `days` SMALLINT UNSIGNED NOT NULL AFTER `join` ; + +ALTER TABLE `zt_case` CHANGE `lastRun` `lastRunDate` DATETIME NOT NULL , +CHANGE `lastResult` `lastRunResult` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_testRun` CHANGE `lastRun` `lastRunDate` DATETIME NOT NULL , +CHANGE `lastResult` `lastRunResult` CHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; + +update `zt_task` set `storyVersion` = 1 WHERE `storyVersion` = 0; diff --git a/trunk/db/update2.4.sql b/trunk/db/update2.4.sql new file mode 100644 index 0000000000..e8c3f5eb1b --- /dev/null +++ b/trunk/db/update2.4.sql @@ -0,0 +1,26 @@ +-- fix the error of gender. add commiter field. +ALTER TABLE `zt_user` CHANGE `gendar` `gender` ENUM( 'f', 'm' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'f' ; +ALTER TABLE `zt_user` ADD `commiter` VARCHAR( 100 ) NOT NULL AFTER `nickname` ; + +-- fix the error of objectType. +UPDATE `zt_action` SET `objectType` = 'product' WHERE `objectType` = '`product`'; +UPDATE `zt_action` SET `objectType` = 'productplan' WHERE `objectType` = '`productPlan`'; +UPDATE `zt_action` SET `objectType` = 'project' WHERE `objectType` = '`project`'; +UPDATE `zt_action` SET `objectType` = 'story' WHERE `objectType` = '`story`'; +UPDATE `zt_action` SET `objectType` = 'task' WHERE `objectType` = '`task`'; +UPDATE `zt_action` SET `objectType` = 'bug' WHERE `objectType` = '`bug`'; +UPDATE `zt_action` SET `objectType` = 'case' WHERE `objectType` = '`case`'; +UPDATE `zt_action` SET `objectType` = 'build' WHERE `objectType` = '`build`'; +UPDATE `zt_action` SET `objectType` = 'release' WHERE `objectType` = '`release`'; +UPDATE `zt_action` SET `objectType` = 'user' WHERE `objectType` = '`user`'; +UPDATE `zt_action` SET `objectType` = 'doc' WHERE `objectType` = '`doc`'; +UPDATE `zt_action` SET `objectType` = 'doclib' WHERE `objectType` = '`doclib`'; +UPDATE `zt_action` SET `objectType` = 'testtask' WHERE `objectType` = '`testtask`'; +UPDATE `zt_action` SET `objectType` = 'todo' WHERE `objectType` = '`todo`'; +UPDATE `zt_bug` SET `os` = 'andriod' WHERE `os` = 'adriod'; + +-- add stories and bugs to release and build table. +ALTER TABLE `zt_release` ADD `stories` TEXT NOT NULL AFTER `date` ; +ALTER TABLE `zt_release` ADD `bugs` TEXT NOT NULL AFTER `stories` ; +ALTER TABLE `zt_build` ADD `stories` TEXT NOT NULL AFTER `date` , +ADD `bugs` TEXT NOT NULL AFTER `stories` ; diff --git a/trunk/db/update3.0.beta1.sql b/trunk/db/update3.0.beta1.sql new file mode 100644 index 0000000000..45b7b769a1 --- /dev/null +++ b/trunk/db/update3.0.beta1.sql @@ -0,0 +1,4 @@ +UPDATE `zt_story` SET `source` = 'customer' WHERE `source` = 'custom'; +ALTER TABLE `zt_action` CHANGE `product` `product` VARCHAR( 255 ) NOT NULL ; +ALTER TABLE `zt_history` CHANGE `diff` `diff` MEDIUMTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ; +ALTER TABLE `zt_project` ADD `order` SMALLINT UNSIGNED NOT NULL AFTER `whitelist` ; diff --git a/trunk/db/update3.1.sql b/trunk/db/update3.1.sql new file mode 100644 index 0000000000..0ff97662c1 --- /dev/null +++ b/trunk/db/update3.1.sql @@ -0,0 +1,10 @@ +ALTER TABLE `zt_task` ADD `estStarted` DATE NOT NULL AFTER `assignedDate` , +ADD `realStarted` DATE NOT NULL AFTER `estStarted`; + +ALTER TABLE `zt_config` ADD `module` VARCHAR( 30 ) NOT NULL AFTER `owner` ; +update zt_config set module = 'common'; +update zt_config set company = 1 where `key` = 'sn'; +delete from zt_config where `key` = 'version' and owner = 'system'; +ALTER TABLE `zt_config` CHANGE `value` `value` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; +ALTER TABLE `zt_config` ADD UNIQUE `unique` (`company` , `owner` , `module` , `section` , `key`); +ALTER TABLE `zt_task` ADD `module` MEDIUMINT( 8 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `project` ; diff --git a/trunk/db/update3.2.1.sql b/trunk/db/update3.2.1.sql new file mode 100644 index 0000000000..f6185524e3 --- /dev/null +++ b/trunk/db/update3.2.1.sql @@ -0,0 +1,33 @@ +# zt-task table. +ALTER TABLE `zt_task` ADD INDEX ( `project` ); +ALTER TABLE `zt_task` ADD INDEX ( `status` ); + +# product table. +ALTER TABLE `zt_product` ADD INDEX ( `order` ); + +# projectProduct table. +ALTER TABLE `zt_projectProduct` DROP PRIMARY KEY; +ALTER TABLE `zt_projectProduct` ADD INDEX ( `product` ); +ALTER TABLE `zt_projectProduct` ADD INDEX ( `project` ); + +# zt_team + ALTER TABLE `zt_team` ADD INDEX ( `project` ); + +# zt_module +ALTER TABLE `zt_module` ADD INDEX ( `root` ); +ALTER TABLE `zt_module` ADD INDEX ( `type` ); + +# zt_user +ALTER TABLE `zt_user` DROP INDEX `company`; +ALTER TABLE `zt_user` ADD INDEX ( `company` ); +ALTER TABLE `zt_user` ADD INDEX ( `dept` ); + +# zt_action +ALTER TABLE `zt_action` ADD INDEX ( `date` ); + +# zt_history +ALTER TABLE `zt_history` ADD INDEX ( `action` ); + +# zt_file +ALTER TABLE `zt_file` ADD INDEX ( `objectType` ); +ALTER TABLE `zt_file` ADD INDEX ( `objectID` ); diff --git a/trunk/db/update3.2.sql b/trunk/db/update3.2.sql new file mode 100755 index 0000000000..e69de29bb2 diff --git a/trunk/db/zentao.sql b/trunk/db/zentao.sql new file mode 100644 index 0000000000..9828ab422d --- /dev/null +++ b/trunk/db/zentao.sql @@ -0,0 +1,1608 @@ +-- DROP TABLE IF EXISTS `zt_action`; +CREATE TABLE IF NOT EXISTS `zt_action` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `objectType` varchar(30) NOT NULL default '', + `objectID` mediumint(8) unsigned NOT NULL default '0', + `product` varchar(255) NOT NULL, + `project` mediumint(9) NOT NULL, + `actor` varchar(30) NOT NULL default '', + `action` varchar(30) NOT NULL default '', + `date` datetime NOT NULL, + `comment` text NOT NULL, + `extra` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_bug`; +CREATE TABLE IF NOT EXISTS `zt_bug` ( + `id` mediumint(8) NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL default '0', + `module` mediumint(8) unsigned NOT NULL default '0', + `project` mediumint(8) unsigned NOT NULL default '0', + `story` mediumint(8) unsigned NOT NULL default '0', + `storyVersion` smallint(6) NOT NULL default '1', + `task` mediumint(8) unsigned NOT NULL default '0', + `toTask` mediumint(8) unsigned NOT NULL default '0', + `toStory` mediumint(8) NOT NULL default '0', + `title` varchar(255) NOT NULL, + `keywords` varchar(255) NOT NULL, + `severity` tinyint(4) NOT NULL default '0', + `pri` tinyint(3) unsigned NOT NULL, + `type` varchar(30) NOT NULL default '', + `os` varchar(30) NOT NULL default '', + `browser` varchar(30) NOT NULL default '', + `hardware` varchar(30) NOT NULL, + `found` varchar(30) NOT NULL default '', + `steps` text NOT NULL, + `status` enum('active','resolved','closed') NOT NULL default 'active', + `confirmed` tinyint(1) NOT NULL default '0', + `activatedCount` smallint(6) NOT NULL, + `mailto` varchar(255) NOT NULL default '', + `openedBy` varchar(30) NOT NULL default '', + `openedDate` datetime NOT NULL, + `openedBuild` varchar(255) NOT NULL, + `assignedTo` varchar(30) NOT NULL default '', + `assignedDate` datetime NOT NULL, + `resolvedBy` varchar(30) NOT NULL default '', + `resolution` varchar(30) NOT NULL default '', + `resolvedBuild` varchar(30) NOT NULL default '', + `resolvedDate` datetime NOT NULL, + `closedBy` varchar(30) NOT NULL default '', + `closedDate` datetime NOT NULL, + `duplicateBug` mediumint(8) unsigned NOT NULL, + `linkBug` varchar(255) NOT NULL, + `case` mediumint(8) unsigned NOT NULL, + `caseVersion` smallint(6) NOT NULL default '1', + `result` mediumint(8) unsigned NOT NULL, + `lastEditedBy` varchar(30) NOT NULL default '', + `lastEditedDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_build`; +CREATE TABLE IF NOT EXISTS `zt_build` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL default '0', + `project` mediumint(8) unsigned NOT NULL default '0', + `name` char(150) NOT NULL, + `scmPath` char(255) NOT NULL, + `filePath` char(255) NOT NULL, + `date` date NOT NULL, + `stories` text NOT NULL, + `bugs` text NOT NULL, + `builder` char(30) NOT NULL default '', + `desc` text NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_burn`; +CREATE TABLE IF NOT EXISTS `zt_burn` ( + `company` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL, + `date` date NOT NULL, + `left` float NOT NULL, + `consumed` float NOT NULL, + PRIMARY KEY (`project`,`date`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_case`; +CREATE TABLE IF NOT EXISTS `zt_case` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL default '0', + `module` mediumint(8) unsigned NOT NULL default '0', + `path` mediumint(8) unsigned NOT NULL default '0', + `story` mediumint(30) unsigned NOT NULL default '0', + `storyVersion` smallint(6) NOT NULL default '1', + `title` varchar(255) NOT NULL, + `precondition` text NOT NULL, + `keywords` varchar(255) NOT NULL, + `pri` tinyint(3) unsigned NOT NULL default '0', + `type` char(30) NOT NULL default '1', + `stage` varchar(255) NOT NULL, + `howRun` varchar(30) NOT NULL, + `scriptedBy` varchar(30) NOT NULL, + `scriptedDate` date NOT NULL, + `scriptStatus` varchar(30) NOT NULL, + `scriptLocation` varchar(255) NOT NULL, + `status` char(30) NOT NULL default '1', + `frequency` enum('1','2','3') NOT NULL default '1', + `order` tinyint(30) unsigned NOT NULL default '0', + `openedBy` char(30) NOT NULL default '', + `openedDate` datetime NOT NULL, + `lastEditedBy` char(30) NOT NULL default '', + `lastEditedDate` datetime NOT NULL, + `version` tinyint(3) unsigned NOT NULL default '0', + `linkCase` varchar(255) NOT NULL, + `fromBug` mediumint(8) unsigned NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + `lastRunner` varchar(30) NOT NULL, + `lastRunDate` datetime NOT NULL, + `lastRunResult` char(30) NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_caseStep`; +CREATE TABLE IF NOT EXISTS `zt_caseStep` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `case` mediumint(8) unsigned NOT NULL default '0', + `version` smallint(3) unsigned NOT NULL default '0', + `desc` text NOT NULL, + `expect` text NOT NULL, + PRIMARY KEY (`id`), + KEY `case` (`case`,`version`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_company`; +CREATE TABLE IF NOT EXISTS `zt_company` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `name` char(120) default NULL, + `phone` char(20) default NULL, + `fax` char(20) default NULL, + `address` char(120) default NULL, + `zipcode` char(10) default NULL, + `website` char(120) default NULL, + `backyard` char(120) default NULL, + `pms` char(120) default NULL, + `guest` enum('1','0') NOT NULL default '0', + `admins` char(255) default NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + UNIQUE KEY `pms` (`pms`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_config`; +CREATE TABLE IF NOT EXISTS `zt_config` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `owner` char(30) NOT NULL default '', + `module` varchar(30) NOT NULL, + `section` char(30) NOT NULL default '', + `key` char(30) NOT NULL default '', + `value` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique` (`company`,`owner`,`module`,`section`,`key`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_dept`; +CREATE TABLE IF NOT EXISTS `zt_dept` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `name` char(60) NOT NULL, + `parent` mediumint(8) unsigned NOT NULL default '0', + `path` char(255) NOT NULL default '', + `grade` tinyint(3) unsigned NOT NULL default '0', + `order` tinyint(3) unsigned NOT NULL default '0', + `position` char(30) NOT NULL default '', + `function` char(255) NOT NULL default '', + `manager` char(30) NOT NULL default '', + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_doc`; +CREATE TABLE IF NOT EXISTS `zt_doc` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` smallint(5) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL, + `lib` varchar(30) NOT NULL, + `module` varchar(30) NOT NULL, + `title` varchar(255) NOT NULL, + `digest` varchar(255) NOT NULL, + `keywords` varchar(255) NOT NULL, + `type` varchar(30) NOT NULL, + `content` text NOT NULL, + `url` varchar(255) NOT NULL, + `views` smallint(5) unsigned NOT NULL, + `addedBy` varchar(30) NOT NULL, + `addedDate` datetime NOT NULL, + `editedBy` varchar(30) NOT NULL, + `editedDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_docLib`; +CREATE TABLE IF NOT EXISTS `zt_docLib` ( + `id` smallint(5) unsigned NOT NULL auto_increment, + `company` smallint(5) unsigned NOT NULL, + `name` varchar(60) NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_effort`; +CREATE TABLE IF NOT EXISTS `zt_effort` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `user` char(30) NOT NULL default '', + `todo` enum('1','0') NOT NULL default '1', + `date` date NOT NULL default '0000-00-00', + `begin` datetime NOT NULL default '0000-00-00 00:00:00', + `end` datetime NOT NULL default '0000-00-00 00:00:00', + `type` enum('1','2','3') NOT NULL default '1', + `idvalue` mediumint(8) unsigned NOT NULL default '0', + `name` char(30) NOT NULL default '', + `desc` char(255) NOT NULL default '', + `status` enum('1','2','3') NOT NULL default '1', + PRIMARY KEY (`id`), + KEY `user` (`user`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_extension`; +CREATE TABLE IF NOT EXISTS `zt_extension` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `name` varchar(150) NOT NULL, + `code` varchar(30) NOT NULL, + `version` varchar(50) NOT NULL, + `author` varchar(100) NOT NULL, + `desc` text NOT NULL, + `license` text NOT NULL, + `type` varchar(20) NOT NULL default 'extension', + `site` varchar(150) NOT NULL, + `zentaoVersion` varchar(100) NOT NULL, + `installedTime` datetime NOT NULL, + `dirs` text NOT NULL, + `files` text NOT NULL, + `status` varchar(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `code` (`code`), + KEY `name` (`name`), + KEY `addedTime` (`installedTime`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_file`; +CREATE TABLE IF NOT EXISTS `zt_file` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `pathname` char(50) NOT NULL, + `title` char(90) NOT NULL, + `extension` char(30) NOT NULL, + `size` mediumint(8) unsigned NOT NULL default '0', + `objectType` char(30) NOT NULL, + `objectID` mediumint(9) NOT NULL, + `addedBy` char(30) NOT NULL default '', + `addedDate` datetime NOT NULL, + `downloads` mediumint(8) unsigned NOT NULL default '0', + `extra` varchar(255) NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_group`; +CREATE TABLE IF NOT EXISTS `zt_group` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `name` char(30) NOT NULL, + `desc` char(255) NOT NULL default '', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_groupPriv`; +CREATE TABLE IF NOT EXISTS `zt_groupPriv` ( + `company` mediumint(9) NOT NULL, + `group` mediumint(8) unsigned NOT NULL default '0', + `module` char(30) NOT NULL default '', + `method` char(30) NOT NULL default '', + UNIQUE KEY `group` (`group`,`module`,`method`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_history`; +CREATE TABLE IF NOT EXISTS `zt_history` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `action` mediumint(8) unsigned NOT NULL default '0', + `field` varchar(30) NOT NULL default '', + `old` text NOT NULL, + `new` text NOT NULL, + `diff` mediumtext NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_module`; +CREATE TABLE IF NOT EXISTS `zt_module` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `root` mediumint(8) unsigned NOT NULL default '0', + `name` char(60) NOT NULL default '', + `parent` mediumint(8) unsigned NOT NULL default '0', + `path` char(255) NOT NULL default '', + `grade` tinyint(3) unsigned NOT NULL default '0', + `order` tinyint(3) unsigned NOT NULL default '0', + `type` char(30) NOT NULL, + `owner` varchar(30) NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_product`; +CREATE TABLE IF NOT EXISTS `zt_product` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `name` varchar(90) NOT NULL, + `code` varchar(45) NOT NULL, + `order` tinyint(3) unsigned NOT NULL default '0', + `status` varchar(30) NOT NULL default '', + `desc` text NOT NULL, + `PO` varchar(30) NOT NULL, + `QM` varchar(30) NOT NULL, + `RM` varchar(30) NOT NULL, + `acl` enum('open','private','custom') NOT NULL default 'open', + `whitelist` varchar(255) NOT NULL, + `createdBy` varchar(30) NOT NULL, + `createdDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_productPlan`; +CREATE TABLE IF NOT EXISTS `zt_productPlan` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL, + `title` varchar(90) NOT NULL, + `desc` text NOT NULL, + `begin` date NOT NULL, + `end` date NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_project`; +CREATE TABLE IF NOT EXISTS `zt_project` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `isCat` enum('1','0') NOT NULL default '0', + `catID` mediumint(8) unsigned NOT NULL, + `type` enum('sprint','project') NOT NULL default 'sprint', + `parent` mediumint(8) unsigned NOT NULL default '0', + `name` varchar(90) NOT NULL, + `code` varchar(45) NOT NULL, + `begin` date NOT NULL, + `end` date NOT NULL, + `days` smallint(5) unsigned NOT NULL, + `status` varchar(10) NOT NULL, + `statge` enum('1','2','3','4','5') NOT NULL default '1', + `pri` enum('1','2','3','4') NOT NULL default '1', + `desc` text NOT NULL, + `goal` text NOT NULL, + `openedBy` varchar(30) NOT NULL default '', + `openedDate` int(10) unsigned NOT NULL default '0', + `closedBy` varchar(30) NOT NULL default '', + `closedDate` int(10) unsigned NOT NULL default '0', + `canceledBy` varchar(30) NOT NULL default '', + `canceledDate` int(10) unsigned NOT NULL default '0', + `PO` varchar(30) NOT NULL default '', + `PM` varchar(30) NOT NULL default '', + `QM` varchar(30) NOT NULL default '', + `RM` varchar(30) NOT NULL default '', + `team` varchar(30) NOT NULL, + `acl` enum('open','private','custom') NOT NULL default 'open', + `whitelist` varchar(255) NOT NULL, + `order` smallint(5) unsigned NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `company` (`company`,`type`,`parent`,`begin`,`end`,`status`,`statge`,`pri`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_projectProduct`; +CREATE TABLE IF NOT EXISTS `zt_projectProduct` ( + `company` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL, + PRIMARY KEY (`project`,`product`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_projectStory`; +CREATE TABLE IF NOT EXISTS `zt_projectStory` ( + `company` mediumint(9) NOT NULL, + `project` mediumint(8) unsigned NOT NULL default '0', + `product` mediumint(8) unsigned NOT NULL, + `story` mediumint(8) unsigned NOT NULL default '0', + `version` smallint(6) NOT NULL default '1', + UNIQUE KEY `project` (`project`,`story`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_release`; +CREATE TABLE IF NOT EXISTS `zt_release` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL default '0', + `build` mediumint(8) unsigned NOT NULL, + `name` char(30) NOT NULL default '', + `date` date NOT NULL, + `stories` text NOT NULL, + `bugs` text NOT NULL, + `desc` text NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_story`; +CREATE TABLE IF NOT EXISTS `zt_story` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `product` mediumint(8) unsigned NOT NULL default '0', + `module` mediumint(8) unsigned NOT NULL default '0', + `plan` mediumint(8) unsigned NOT NULL default '0', + `source` varchar(20) NOT NULL, + `fromBug` mediumint(8) unsigned NOT NULL default '0', + `title` varchar(255) NOT NULL, + `keywords` varchar(255) NOT NULL, + `type` varchar(30) NOT NULL default '', + `pri` tinyint(3) unsigned NOT NULL default '3', + `estimate` float unsigned NOT NULL, + `status` varchar(30) NOT NULL default '', + `stage` varchar(30) NOT NULL, + `mailto` varchar(255) NOT NULL default '', + `openedBy` varchar(30) NOT NULL default '', + `openedDate` datetime NOT NULL, + `assignedTo` varchar(30) NOT NULL default '', + `assignedDate` datetime NOT NULL, + `lastEditedBy` varchar(30) NOT NULL default '', + `lastEditedDate` datetime NOT NULL, + `reviewedBy` varchar(30) NOT NULL, + `reviewedDate` date NOT NULL, + `closedBy` varchar(30) NOT NULL default '', + `closedDate` datetime NOT NULL, + `closedReason` varchar(30) NOT NULL, + `toBug` mediumint(9) NOT NULL, + `childStories` varchar(255) NOT NULL, + `linkStories` varchar(255) NOT NULL, + `duplicateStory` mediumint(8) unsigned NOT NULL, + `version` smallint(6) NOT NULL default '1', + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `product` (`product`,`module`,`plan`,`type`,`pri`), + KEY `status` (`status`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_storySpec`; +CREATE TABLE IF NOT EXISTS `zt_storySpec` ( + `company` mediumint(8) unsigned NOT NULL, + `story` mediumint(9) NOT NULL, + `version` smallint(6) NOT NULL, + `title` varchar(90) NOT NULL, + `spec` text NOT NULL, + `verify` text NOT NULL, + UNIQUE KEY `story` (`story`,`version`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_task`; +CREATE TABLE IF NOT EXISTS `zt_task` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL default '0', + `module` mediumint(8) unsigned NOT NULL default '0', + `story` mediumint(8) unsigned NOT NULL default '0', + `storyVersion` smallint(6) NOT NULL default '1', + `fromBug` mediumint(8) unsigned NOT NULL default '0', + `name` varchar(255) NOT NULL, + `type` varchar(20) NOT NULL, + `pri` tinyint(3) unsigned NOT NULL default '0', + `estimate` float unsigned NOT NULL, + `consumed` float unsigned NOT NULL, + `left` float unsigned NOT NULL, + `deadline` date NOT NULL, + `status` enum('wait','doing','done','cancel','closed') NOT NULL default 'wait', + `statusCustom` tinyint(3) unsigned NOT NULL, + `mailto` varchar(255) NOT NULL default '', + `desc` text NOT NULL, + `openedBy` varchar(30) NOT NULL, + `openedDate` datetime NOT NULL, + `assignedTo` varchar(30) NOT NULL, + `assignedDate` datetime NOT NULL, + `estStarted` date NOT NULL, + `realStarted` date NOT NULL, + `finishedBy` varchar(30) NOT NULL, + `finishedDate` datetime NOT NULL, + `canceledBy` varchar(30) NOT NULL, + `canceledDate` datetime NOT NULL, + `closedBy` varchar(30) NOT NULL, + `closedDate` datetime NOT NULL, + `closedReason` varchar(30) NOT NULL, + `lastEditedBy` varchar(30) NOT NULL, + `lastEditedDate` datetime NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `statusOrder` (`statusCustom`), + KEY `type` (`type`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_taskEstimate`; +CREATE TABLE IF NOT EXISTS `zt_taskEstimate` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `task` mediumint(8) unsigned NOT NULL default '0', + `date` int(10) unsigned NOT NULL default '0', + `estimate` tinyint(3) unsigned NOT NULL default '0', + `estimater` char(30) NOT NULL default '', + PRIMARY KEY (`id`), + KEY `task` (`task`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_team`; +CREATE TABLE IF NOT EXISTS `zt_team` ( + `company` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL default '0', + `account` char(30) NOT NULL default '', + `role` char(30) NOT NULL default '', + `join` date NOT NULL default '0000-00-00', + `days` smallint(5) unsigned NOT NULL, + `hours` tinyint(3) unsigned NOT NULL default '0', + PRIMARY KEY (`project`,`account`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_testResult`; +CREATE TABLE IF NOT EXISTS `zt_testResult` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `run` mediumint(8) unsigned NOT NULL, + `case` mediumint(8) unsigned NOT NULL, + `version` smallint(5) unsigned NOT NULL, + `caseResult` char(30) NOT NULL, + `stepResults` text NOT NULL, + `lastRunner` varchar(30) NOT NULL, + `date` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `run` (`run`), + KEY `case` (`case`,`version`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_testRun`; +CREATE TABLE IF NOT EXISTS `zt_testRun` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `task` mediumint(8) unsigned NOT NULL default '0', + `case` mediumint(8) unsigned NOT NULL default '0', + `version` tinyint(3) unsigned NOT NULL default '0', + `assignedTo` char(30) NOT NULL default '', + `lastRunner` varchar(30) NOT NULL, + `lastRunDate` datetime NOT NULL, + `lastRunResult` char(30) NOT NULL, + `status` char(30) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `task` (`task`,`case`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_testTask`; +CREATE TABLE IF NOT EXISTS `zt_testTask` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `name` char(90) NOT NULL, + `product` mediumint(8) unsigned NOT NULL, + `project` mediumint(8) unsigned NOT NULL default '0', + `build` char(30) NOT NULL, + `owner` varchar(30) NOT NULL, + `begin` date NOT NULL, + `end` date NOT NULL, + `desc` text NOT NULL, + `status` char(30) NOT NULL, + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_todo`; +CREATE TABLE IF NOT EXISTS `zt_todo` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(9) NOT NULL, + `account` char(30) NOT NULL, + `date` date NOT NULL default '0000-00-00', + `begin` smallint(4) unsigned zerofill NOT NULL, + `end` smallint(4) unsigned zerofill NOT NULL, + `type` char(10) NOT NULL, + `idvalue` mediumint(8) unsigned NOT NULL default '0', + `pri` tinyint(3) unsigned NOT NULL, + `name` char(150) NOT NULL, + `desc` text NOT NULL, + `status` char(20) NOT NULL default '', + `private` tinyint(1) NOT NULL, + PRIMARY KEY (`id`), + KEY `user` (`account`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_user`; +CREATE TABLE IF NOT EXISTS `zt_user` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `dept` mediumint(8) unsigned NOT NULL default '0', + `account` char(30) NOT NULL default '', + `password` char(32) NOT NULL default '', + `realname` char(30) NOT NULL default '', + `nickname` char(60) NOT NULL default '', + `commiter` varchar(100) NOT NULL, + `avatar` char(30) NOT NULL default '', + `birthday` date NOT NULL default '0000-00-00', + `gender` enum('f','m') NOT NULL default 'f', + `email` char(90) NOT NULL default '', + `msn` char(90) NOT NULL default '', + `qq` char(20) NOT NULL default '', + `yahoo` char(90) NOT NULL default '', + `gtalk` char(90) NOT NULL default '', + `wangwang` char(90) NOT NULL default '', + `mobile` char(11) NOT NULL default '', + `phone` char(20) NOT NULL default '', + `address` char(120) NOT NULL default '', + `zipcode` char(10) NOT NULL default '', + `join` date NOT NULL default '0000-00-00', + `visits` mediumint(8) unsigned NOT NULL default '0', + `ip` char(15) NOT NULL default '', + `last` int(10) unsigned NOT NULL default '0', + `deleted` enum('0','1') NOT NULL default '0', + PRIMARY KEY (`id`), + UNIQUE KEY `account` (`account`), + KEY `company` (`company`,`dept`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_userGroup`; +CREATE TABLE IF NOT EXISTS `zt_userGroup` ( + `company` mediumint(8) unsigned NOT NULL, + `account` char(30) NOT NULL default '', + `group` mediumint(8) unsigned NOT NULL default '0', + UNIQUE KEY `account` (`account`,`group`), + KEY `company` (`company`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_userQuery`; +CREATE TABLE IF NOT EXISTS `zt_userQuery` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL default '0', + `account` char(30) NOT NULL, + `module` varchar(30) NOT NULL, + `title` varchar(90) NOT NULL, + `form` text NOT NULL, + `sql` text NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`), + KEY `account` (`account`), + KEY `module` (`module`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_userTPL`; +CREATE TABLE IF NOT EXISTS `zt_userTPL` ( + `id` mediumint(8) unsigned NOT NULL auto_increment, + `company` mediumint(8) unsigned NOT NULL, + `account` char(30) NOT NULL, + `type` char(30) NOT NULL, + `title` varchar(150) NOT NULL, + `content` text NOT NULL, + PRIMARY KEY (`id`), + KEY `company` (`company`), + KEY `account` (`account`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +INSERT INTO `zt_group` (`id`, `company`, `name`, `desc`) VALUES +(1, 1, 'ADMIN', 'for administrator.'), +(2, 1, 'PO', 'for product owners.'), +(3, 1, 'DEV', 'for developers.'), +(4, 1, 'QA', 'for testers.'), +(5, 1, 'PM', 'for project managers.'), +(6, 1, 'guest', 'For guest'); + +INSERT INTO `zt_groupPriv` (`company`, `group`, `module`, `method`) VALUES +(1, 1, 'action', 'trash'), +(1, 1, 'misc', 'ping'), +(1, 1, 'file', 'ajaxUpload'), +(1, 1, 'file', 'delete'), +(1, 1, 'file', 'edit'), +(1, 1, 'file', 'download'), +(1, 1, 'api', 'getModel'), +(1, 1, 'extension', 'upgrade'), +(1, 1, 'extension', 'erase'), +(1, 1, 'extension', 'upload'), +(1, 1, 'extension', 'deactivate'), +(1, 1, 'extension', 'activate'), +(1, 1, 'extension', 'uninstall'), +(1, 1, 'extension', 'install'), +(1, 1, 'extension', 'structure'), +(1, 1, 'extension', 'obtain'), +(1, 1, 'extension', 'browse'), +(1, 1, 'search', 'select'), +(1, 1, 'search', 'deleteQuery'), +(1, 1, 'search', 'saveQuery'), +(1, 1, 'search', 'buildQuery'), +(1, 1, 'search', 'buildForm'), +(1, 1, 'tree', 'fix'), +(1, 1, 'tree', 'ajaxGetSonModules'), +(1, 1, 'tree', 'ajaxGetOptionMenu'), +(1, 1, 'tree', 'delete'), +(1, 1, 'tree', 'edit'), +(1, 1, 'tree', 'manageChild'), +(1, 1, 'tree', 'updateOrder'), +(1, 1, 'tree', 'browse'), +(1, 1, 'user', 'ajaxGetUser'), +(1, 1, 'user', 'profile'), +(1, 1, 'user', 'dynamic'), +(1, 1, 'user', 'project'), +(1, 1, 'user', 'bug'), +(1, 1, 'user', 'task'), +(1, 1, 'user', 'todo'), +(1, 1, 'user', 'delete'), +(1, 1, 'user', 'edit'), +(1, 1, 'user', 'view'), +(1, 1, 'user', 'create'), +(1, 1, 'group', 'manageMember'), +(1, 1, 'group', 'managePriv'), +(1, 1, 'group', 'delete'), +(1, 1, 'group', 'copy'), +(1, 1, 'group', 'edit'), +(1, 1, 'group', 'create'), +(1, 1, 'group', 'browse'), +(1, 1, 'dept', 'delete'), +(1, 1, 'dept', 'manageChild'), +(1, 1, 'dept', 'updateOrder'), +(1, 1, 'dept', 'browse'), +(1, 1, 'company', 'dynamic'), +(1, 1, 'company', 'edit'), +(1, 1, 'company', 'browse'), +(1, 1, 'company', 'index'), +(1, 1, 'svn', 'apiSync'), +(1, 1, 'svn', 'cat'), +(1, 1, 'svn', 'diff'), +(1, 1, 'doc', 'delete'), +(1, 1, 'doc', 'edit'), +(1, 1, 'doc', 'view'), +(1, 1, 'doc', 'create'), +(1, 1, 'doc', 'deleteLib'), +(1, 1, 'doc', 'editLib'), +(1, 1, 'doc', 'createLib'), +(1, 1, 'doc', 'browse'), +(1, 1, 'doc', 'index'), +(1, 1, 'testtask', 'results'), +(1, 1, 'testtask', 'runcase'), +(1, 1, 'testtask', 'unlinkcase'), +(1, 1, 'testtask', 'linkcase'), +(1, 1, 'testtask', 'batchAssign'), +(1, 1, 'testtask', 'delete'), +(1, 1, 'testtask', 'edit'), +(1, 1, 'testtask', 'cases'), +(1, 1, 'testtask', 'view'), +(1, 1, 'testtask', 'browse'), +(1, 1, 'testtask', 'create'), +(1, 1, 'testtask', 'index'), +(1, 1, 'testcase', 'confirmStoryChange'), +(1, 1, 'testcase', 'export'), +(1, 1, 'testcase', 'delete'), +(1, 1, 'testcase', 'edit'), +(1, 1, 'testcase', 'view'), +(1, 1, 'testcase', 'batchCreate'), +(1, 1, 'testcase', 'create'), +(1, 1, 'testcase', 'browse'), +(1, 1, 'testcase', 'index'), +(1, 1, 'bug', 'ajaxGetModuleOwner'), +(1, 1, 'bug', 'ajaxGetUserBugs'), +(1, 1, 'bug', 'customFields'), +(1, 1, 'bug', 'deleteTemplate'), +(1, 1, 'bug', 'saveTemplate'), +(1, 1, 'bug', 'delete'), +(1, 1, 'bug', 'confirmStoryChange'), +(1, 1, 'bug', 'export'), +(1, 1, 'bug', 'report'), +(1, 1, 'bug', 'close'), +(1, 1, 'bug', 'activate'), +(1, 1, 'bug', 'resolve'), +(1, 1, 'bug', 'assignTo'), +(1, 1, 'bug', 'edit'), +(1, 1, 'bug', 'view'), +(1, 1, 'bug', 'confirmBug'), +(1, 1, 'bug', 'create'), +(1, 1, 'bug', 'browse'), +(1, 1, 'bug', 'index'), +(1, 1, 'qa', 'index'), +(1, 1, 'build', 'ajaxGetProjectBuilds'), +(1, 1, 'build', 'ajaxGetProductBuilds'), +(1, 1, 'build', 'view'), +(1, 1, 'build', 'delete'), +(1, 1, 'build', 'edit'), +(1, 1, 'build', 'create'), +(1, 1, 'task', 'report'), +(1, 1, 'task', 'ajaxGetProjectTasks'), +(1, 1, 'task', 'ajaxGetUserTasks'), +(1, 1, 'task', 'confirmStoryChange'), +(1, 1, 'task', 'export'), +(1, 1, 'task', 'view'), +(1, 1, 'task', 'delete'), +(1, 1, 'task', 'activate'), +(1, 1, 'task', 'batchClose'), +(1, 1, 'task', 'close'), +(1, 1, 'task', 'cancel'), +(1, 1, 'task', 'finish'), +(1, 1, 'task', 'start'), +(1, 1, 'task', 'assignTo'), +(1, 1, 'task', 'edit'), +(1, 1, 'task', 'batchCreate'), +(1, 1, 'task', 'create'), +(1, 1, 'project', 'ajaxGetProducts'), +(1, 1, 'project', 'linkStory'), +(1, 1, 'project', 'unlinkMember'), +(1, 1, 'project', 'manageMembers'), +(1, 1, 'project', 'manageProducts'), +(1, 1, 'project', 'dynamic'), +(1, 1, 'project', 'doc'), +(1, 1, 'project', 'team'), +(1, 1, 'project', 'burnData'), +(1, 1, 'project', 'computeBurn'), +(1, 1, 'project', 'burn'), +(1, 1, 'project', 'bug'), +(1, 1, 'project', 'testtask'), +(1, 1, 'project', 'build'), +(1, 1, 'project', 'story'), +(1, 1, 'project', 'importBug'), +(1, 2, 'bug', 'export'), +(1, 2, 'my', 'testCase'), +(1, 2, 'my', 'story'), +(1, 2, 'task', 'ajaxGetUserTasks'), +(1, 2, 'tree', 'ajaxGetOptionMenu'), +(1, 2, 'my', 'editProfile'), +(1, 2, 'search', 'deleteQuery'), +(1, 2, 'product', 'project'), +(1, 2, 'doc', 'create'), +(1, 2, 'project', 'dynamic'), +(1, 2, 'todo', 'delete'), +(1, 2, 'index', 'index'), +(1, 2, 'company', 'index'), +(1, 2, 'release', 'delete'), +(1, 2, 'svn', 'diff'), +(1, 2, 'doc', 'delete'), +(1, 2, 'search', 'buildForm'), +(1, 2, 'my', 'profile'), +(1, 2, 'user', 'profile'), +(1, 2, 'user', 'project'), +(1, 2, 'user', 'todo'), +(1, 2, 'doc', 'deleteLib'), +(1, 2, 'my', 'dynamic'), +(1, 2, 'project', 'bug'), +(1, 2, 'story', 'create'), +(1, 2, 'todo', 'create'), +(1, 2, 'task', 'view'), +(1, 2, 'bug', 'create'), +(1, 2, 'task', 'export'), +(1, 2, 'report', 'workload'), +(1, 2, 'story', 'activate'), +(1, 2, 'project', 'story'), +(1, 2, 'productplan', 'browse'), +(1, 2, 'bug', 'resolve'), +(1, 2, 'build', 'ajaxGetProjectBuilds'), +(1, 2, 'doc', 'editLib'), +(1, 2, 'task', 'report'), +(1, 2, 'story', 'batchClose'), +(1, 2, 'file', 'ajaxUpload'), +(1, 2, 'product', 'roadmap'), +(1, 2, 'productplan', 'create'), +(1, 2, 'todo', 'view'), +(1, 2, 'search', 'select'), +(1, 2, 'group', 'browse'), +(1, 2, 'svn', 'cat'), +(1, 2, 'misc', 'ping'), +(1, 2, 'project', 'team'), +(1, 2, 'file', 'delete'), +(1, 2, 'report', 'index'), +(1, 2, 'productplan', 'unlinkStory'), +(1, 2, 'bug', 'ajaxGetUserBugs'), +(1, 2, 'release', 'ajaxGetStoriesAndBugs'), +(1, 2, 'testcase', 'browse'), +(1, 2, 'project', 'testtask'), +(1, 2, 'search', 'buildQuery'), +(1, 2, 'bug', 'ajaxGetModuleOwner'), +(1, 2, 'tree', 'manageChild'), +(1, 2, 'my', 'task'), +(1, 2, 'my', 'testTask'), +(1, 2, 'bug', 'edit'), +(1, 2, 'user', 'view'), +(1, 2, 'productplan', 'linkStory'), +(1, 2, 'story', 'edit'), +(1, 2, 'product', 'ajaxGetProjects'), +(1, 2, 'project', 'burn'), +(1, 2, 'story', 'report'), +(1, 2, 'product', 'ajaxGetPlans'), +(1, 2, 'tree', 'fix'), +(1, 2, 'tree', 'delete'), +(1, 2, 'user', 'dynamic'), +(1, 2, 'story', 'tasks'), +(1, 2, 'tree', 'updateOrder'), +(1, 2, 'user', 'task'), +(1, 2, 'product', 'delete'), +(1, 2, 'release', 'create'), +(1, 2, 'bug', 'index'), +(1, 2, 'doc', 'createLib'), +(1, 2, 'doc', 'edit'), +(1, 3, 'testtask', 'index'), +(1, 3, 'testcase', 'browse'), +(1, 3, 'doc', 'deleteLib'), +(1, 3, 'doc', 'delete'), +(1, 3, 'bug', 'saveTemplate'), +(1, 3, 'bug', 'create'), +(1, 3, 'testtask', 'view'), +(1, 3, 'task', 'confirmStoryChange'), +(1, 3, 'project', 'browse'), +(1, 3, 'product', 'browse'), +(1, 3, 'my', 'profile'), +(1, 3, 'product', 'dynamic'), +(1, 3, 'testcase', 'export'), +(1, 3, 'search', 'deleteQuery'), +(1, 3, 'file', 'delete'), +(1, 3, 'my', 'task'), +(1, 3, 'todo', 'delete'), +(1, 3, 'testtask', 'cases'), +(1, 3, 'story', 'report'), +(1, 3, 'my', 'changePassword'), +(1, 3, 'misc', 'ping'), +(1, 3, 'search', 'buildQuery'), +(1, 3, 'project', 'build'), +(1, 3, 'bug', 'export'), +(1, 3, 'story', 'view'), +(1, 3, 'qa', 'index'), +(1, 3, 'file', 'ajaxUpload'), +(1, 3, 'search', 'saveQuery'), +(1, 3, 'task', 'cancel'), +(1, 3, 'build', 'view'), +(1, 3, 'doc', 'create'), +(1, 3, 'task', 'ajaxGetProjectTasks'), +(1, 3, 'product', 'ajaxGetPlans'), +(1, 3, 'doc', 'createLib'), +(1, 3, 'bug', 'ajaxGetUserBugs'), +(1, 3, 'bug', 'ajaxGetModuleOwner'), +(1, 3, 'search', 'buildForm'), +(1, 3, 'bug', 'edit'), +(1, 3, 'doc', 'index'), +(1, 3, 'story', 'ajaxGetProjectStories'), +(1, 3, 'testtask', 'results'), +(1, 3, 'task', 'delete'), +(1, 3, 'product', 'project'), +(1, 3, 'product', 'ajaxGetProjects'), +(1, 3, 'product', 'index'), +(1, 3, 'my', 'story'), +(1, 3, 'file', 'edit'), +(1, 3, 'task', 'export'), +(1, 3, 'product', 'doc'), +(1, 3, 'my', 'bug'), +(1, 3, 'build', 'ajaxGetProjectBuilds'), +(1, 3, 'testtask', 'browse'), +(1, 3, 'task', 'activate'), +(1, 3, 'file', 'download'), +(1, 4, 'search', 'buildForm'), +(1, 3, 'my', 'dynamic'), +(1, 3, 'product', 'roadmap'), +(1, 3, 'doc', 'browse'), +(1, 4, 'testtask', 'results'), +(1, 4, 'doc', 'createLib'), +(1, 4, 'product', 'ajaxGetPlans'), +(1, 4, 'tree', 'manageChild'), +(1, 4, 'build', 'ajaxGetProjectBuilds'), +(1, 4, 'my', 'todo'), +(1, 4, 'build', 'view'), +(1, 4, 'my', 'testTask'), +(1, 4, 'testtask', 'unlinkcase'), +(1, 4, 'bug', 'delete'), +(1, 4, 'project', 'burn'), +(1, 4, 'productplan', 'browse'), +(1, 4, 'project', 'grouptask'), +(1, 4, 'doc', 'create'), +(1, 4, 'misc', 'ping'), +(1, 4, 'testtask', 'batchAssign'), +(1, 4, 'bug', 'view'), +(1, 4, 'bug', 'confirmBug'), +(1, 4, 'story', 'tasks'), +(1, 4, 'product', 'project'), +(1, 4, 'release', 'browse'), +(1, 4, 'bug', 'customFields'), +(1, 4, 'tree', 'delete'), +(1, 4, 'testcase', 'edit'), +(1, 4, 'svn', 'cat'), +(1, 4, 'qa', 'index'), +(1, 4, 'productplan', 'view'), +(1, 4, 'testtask', 'delete'), +(1, 4, 'doc', 'deleteLib'), +(1, 4, 'bug', 'saveTemplate'), +(1, 4, 'search', 'saveQuery'), +(1, 4, 'testcase', 'index'), +(1, 4, 'project', 'build'), +(1, 4, 'doc', 'browse'), +(1, 4, 'bug', 'index'), +(1, 4, 'bug', 'ajaxGetUserBugs'), +(1, 4, 'testcase', 'delete'), +(1, 4, 'bug', 'report'), +(1, 4, 'file', 'delete'), +(1, 4, 'testtask', 'cases'), +(1, 4, 'file', 'ajaxUpload'), +(1, 4, 'build', 'ajaxGetProductBuilds'), +(1, 4, 'tree', 'updateOrder'), +(1, 4, 'product', 'ajaxGetProjects'), +(1, 4, 'project', 'browse'), +(1, 4, 'project', 'story'), +(1, 4, 'story', 'report'), +(1, 4, 'bug', 'browse'), +(1, 4, 'search', 'select'), +(1, 4, 'svn', 'diff'), +(1, 4, 'project', 'bug'), +(1, 4, 'todo', 'delete'), +(1, 4, 'testtask', 'create'), +(1, 4, 'my', 'task'), +(1, 4, 'user', 'ajaxGetUser'), +(1, 4, 'user', 'task'), +(1, 4, 'project', 'doc'), +(1, 4, 'tree', 'edit'), +(1, 4, 'release', 'ajaxGetStoriesAndBugs'), +(1, 4, 'project', 'view'), +(1, 4, 'release', 'export'), +(1, 4, 'company', 'index'), +(1, 4, 'user', 'profile'), +(1, 4, 'bug', 'edit'), +(1, 4, 'svn', 'apiSync'), +(1, 4, 'index', 'index'), +(1, 4, 'doc', 'edit'), +(1, 4, 'bug', 'export'), +(1, 4, 'todo', 'edit'), +(1, 4, 'file', 'edit'), +(1, 4, 'bug', 'resolve'), +(1, 4, 'release', 'view'), +(1, 4, 'project', 'index'), +(1, 5, 'misc', 'ping'), +(1, 5, 'file', 'ajaxUpload'), +(1, 5, 'file', 'delete'), +(1, 5, 'file', 'edit'), +(1, 5, 'file', 'download'), +(1, 5, 'tree', 'fix'), +(1, 5, 'tree', 'ajaxGetSonModules'), +(1, 5, 'tree', 'ajaxGetOptionMenu'), +(1, 5, 'tree', 'delete'), +(1, 5, 'tree', 'edit'), +(1, 5, 'tree', 'manageChild'), +(1, 5, 'tree', 'updateOrder'), +(1, 5, 'tree', 'browse'), +(1, 5, 'search', 'select'), +(1, 5, 'search', 'deleteQuery'), +(1, 5, 'search', 'saveQuery'), +(1, 5, 'search', 'buildQuery'), +(1, 5, 'search', 'buildForm'), +(1, 5, 'svn', 'apiSync'), +(1, 5, 'svn', 'cat'), +(1, 5, 'svn', 'diff'), +(1, 5, 'action', 'undelete'), +(1, 5, 'action', 'trash'), +(1, 5, 'user', 'ajaxGetUser'), +(1, 5, 'user', 'profile'), +(1, 5, 'user', 'dynamic'), +(1, 5, 'user', 'project'), +(1, 5, 'user', 'bug'), +(1, 5, 'user', 'task'), +(1, 5, 'user', 'todo'), +(1, 5, 'user', 'view'), +(1, 5, 'group', 'browse'), +(1, 5, 'company', 'dynamic'), +(1, 5, 'company', 'browse'), +(1, 5, 'company', 'index'), +(1, 5, 'report', 'workload'), +(1, 5, 'report', 'bugSummary'), +(1, 5, 'report', 'productInfo'), +(1, 5, 'report', 'projectDeviation'), +(1, 5, 'report', 'index'), +(1, 5, 'doc', 'edit'), +(1, 5, 'doc', 'view'), +(1, 5, 'doc', 'create'), +(1, 5, 'doc', 'editLib'), +(1, 5, 'doc', 'createLib'), +(1, 5, 'doc', 'browse'), +(1, 5, 'doc', 'index'), +(1, 5, 'testtask', 'results'), +(1, 5, 'testtask', 'edit'), +(1, 5, 'testtask', 'cases'), +(1, 5, 'testtask', 'view'), +(1, 5, 'testtask', 'browse'), +(1, 5, 'testtask', 'create'), +(1, 5, 'testtask', 'index'), +(1, 5, 'testcase', 'confirmStoryChange'), +(1, 5, 'testcase', 'export'), +(1, 5, 'testcase', 'delete'), +(1, 5, 'testcase', 'edit'), +(1, 5, 'testcase', 'view'), +(1, 5, 'testcase', 'batchCreate'), +(1, 5, 'testcase', 'create'), +(1, 1, 'project', 'importtask'), +(1, 1, 'project', 'grouptask'), +(1, 1, 'project', 'task'), +(1, 2, 'release', 'view'), +(1, 2, 'my', 'project'), +(1, 3, 'task', 'report'), +(1, 3, 'task', 'close'), +(1, 3, 'bug', 'view'), +(1, 4, 'testcase', 'browse'), +(1, 4, 'report', 'index'), +(1, 4, 'testtask', 'linkcase'), +(1, 5, 'testcase', 'browse'), +(1, 5, 'testcase', 'index'), +(1, 1, 'project', 'delete'), +(1, 1, 'project', 'order'), +(1, 1, 'project', 'edit'), +(1, 2, 'my', 'bug'), +(1, 4, 'search', 'buildQuery'), +(1, 3, 'index', 'index'), +(1, 4, 'testtask', 'runcase'), +(1, 5, 'bug', 'ajaxGetModuleOwner'), +(1, 5, 'bug', 'ajaxGetUserBugs'), +(1, 5, 'bug', 'customFields'), +(1, 1, 'project', 'create'), +(1, 1, 'project', 'browse'), +(1, 1, 'project', 'view'), +(1, 1, 'project', 'index'), +(1, 1, 'release', 'export'), +(1, 1, 'release', 'ajaxGetStoriesAndBugs'), +(1, 1, 'release', 'view'), +(1, 1, 'release', 'delete'), +(1, 1, 'release', 'edit'), +(1, 1, 'release', 'create'), +(1, 1, 'release', 'browse'), +(1, 1, 'productplan', 'unlinkStory'), +(1, 1, 'productplan', 'linkStory'), +(1, 1, 'productplan', 'view'), +(1, 1, 'productplan', 'delete'), +(1, 1, 'productplan', 'edit'), +(1, 1, 'productplan', 'create'), +(1, 2, 'file', 'edit'), +(1, 2, 'search', 'saveQuery'), +(1, 2, 'doc', 'view'), +(1, 2, 'tree', 'edit'), +(1, 2, 'bug', 'report'), +(1, 2, 'story', 'review'), +(1, 2, 'svn', 'apiSync'), +(1, 2, 'bug', 'view'), +(1, 2, 'user', 'ajaxGetUser'), +(1, 3, 'my', 'editProfile'), +(1, 4, 'search', 'deleteQuery'), +(1, 3, 'story', 'tasks'), +(1, 3, 'report', 'workload'), +(1, 3, 'task', 'batchCreate'), +(1, 3, 'task', 'start'), +(1, 3, 'task', 'batchClose'), +(1, 3, 'story', 'ajaxGetProductStories'), +(1, 3, 'my', 'todo'), +(1, 3, 'user', 'ajaxGetUser'), +(1, 3, 'story', 'export'), +(1, 4, 'user', 'todo'), +(1, 4, 'todo', 'import2Today'), +(1, 4, 'story', 'view'), +(1, 4, 'bug', 'deleteTemplate'), +(1, 4, 'report', 'workload'), +(1, 4, 'testtask', 'browse'), +(1, 4, 'product', 'dynamic'), +(1, 4, 'todo', 'mark'), +(1, 4, 'my', 'profile'), +(1, 4, 'story', 'ajaxGetProductStories'), +(1, 4, 'my', 'dynamic'), +(1, 4, 'task', 'report'), +(1, 5, 'bug', 'deleteTemplate'), +(1, 5, 'bug', 'saveTemplate'), +(1, 5, 'bug', 'export'), +(1, 5, 'bug', 'report'), +(1, 5, 'bug', 'close'), +(1, 5, 'bug', 'activate'), +(1, 5, 'bug', 'resolve'), +(1, 5, 'bug', 'edit'), +(1, 5, 'bug', 'view'), +(1, 5, 'bug', 'confirmBug'), +(1, 5, 'bug', 'create'), +(1, 5, 'bug', 'browse'), +(1, 5, 'bug', 'index'), +(1, 5, 'qa', 'index'), +(1, 1, 'productplan', 'browse'), +(1, 1, 'story', 'ajaxGetProductStories'), +(1, 2, 'todo', 'export'), +(1, 3, 'search', 'select'), +(1, 3, 'todo', 'view'), +(1, 3, 'todo', 'create'), +(1, 3, 'user', 'dynamic'), +(1, 4, 'testtask', 'edit'), +(1, 5, 'build', 'ajaxGetProjectBuilds'), +(1, 5, 'build', 'ajaxGetProductBuilds'), +(1, 5, 'build', 'view'), +(1, 5, 'build', 'delete'), +(1, 5, 'build', 'edit'), +(1, 6, 'testtask', 'view'), +(1, 6, 'bug', 'view'), +(1, 6, 'project', 'view'), +(1, 6, 'doc', 'browse'), +(1, 6, 'bug', 'report'), +(1, 6, 'project', 'grouptask'), +(1, 6, 'project', 'task'), +(1, 6, 'testtask', 'browse'), +(1, 6, 'build', 'view'), +(1, 6, 'user', 'ajaxGetUser'), +(1, 6, 'bug', 'browse'), +(1, 6, 'project', 'index'), +(1, 6, 'project', 'story'), +(1, 6, 'product', 'doc'), +(1, 6, 'user', 'project'), +(1, 6, 'project', 'burn'), +(1, 6, 'task', 'view'), +(1, 6, 'user', 'task'), +(1, 6, 'project', 'build'), +(1, 6, 'project', 'bug'), +(1, 6, 'product', 'index'), +(1, 6, 'project', 'testtask'), +(1, 6, 'project', 'doc'), +(1, 6, 'product', 'roadmap'), +(1, 6, 'product', 'browse'), +(1, 6, 'search', 'buildForm'), +(1, 6, 'product', 'dynamic'), +(1, 6, 'product', 'view'), +(1, 6, 'build', 'ajaxGetProductBuilds'), +(1, 6, 'qa', 'index'), +(1, 6, 'release', 'view'), +(1, 6, 'testtask', 'index'), +(1, 6, 'bug', 'ajaxGetUserBugs'), +(1, 6, 'testcase', 'browse'), +(1, 6, 'task', 'ajaxGetProjectTasks'), +(1, 6, 'build', 'ajaxGetProjectBuilds'), +(1, 6, 'project', 'ajaxGetProducts'), +(1, 6, 'testcase', 'view'), +(1, 6, 'bug', 'ajaxGetModuleOwner'), +(1, 6, 'company', 'browse'), +(1, 6, 'company', 'dynamic'), +(1, 6, 'user', 'profile'), +(1, 6, 'user', 'dynamic'), +(1, 6, 'task', 'ajaxGetUserTasks'), +(1, 6, 'project', 'team'), +(1, 6, 'project', 'dynamic'), +(1, 6, 'story', 'view'), +(1, 6, 'bug', 'index'), +(1, 6, 'story', 'ajaxGetProjectStories'), +(1, 6, 'doc', 'index'), +(1, 6, 'product', 'ajaxGetPlans'), +(1, 6, 'index', 'index'), +(1, 6, 'testtask', 'results'), +(1, 6, 'testcase', 'index'), +(1, 6, 'group', 'browse'), +(1, 6, 'story', 'tasks'), +(1, 6, 'productplan', 'browse'), +(1, 6, 'story', 'ajaxGetProductStories'), +(1, 6, 'productplan', 'view'), +(1, 6, 'search', 'buildQuery'), +(1, 6, 'file', 'download'), +(1, 6, 'user', 'bug'), +(1, 6, 'release', 'browse'), +(1, 6, 'company', 'index'), +(1, 6, 'user', 'todo'), +(1, 6, 'user', 'view'), +(1, 6, 'project', 'browse'), +(1, 1, 'story', 'ajaxGetProjectStories'), +(1, 1, 'story', 'report'), +(1, 1, 'story', 'tasks'), +(1, 1, 'story', 'activate'), +(1, 1, 'story', 'batchClose'), +(1, 1, 'story', 'close'), +(1, 1, 'story', 'review'), +(1, 1, 'story', 'change'), +(1, 1, 'story', 'view'), +(1, 1, 'story', 'delete'), +(1, 1, 'story', 'export'), +(1, 1, 'story', 'edit'), +(1, 1, 'story', 'batchCreate'), +(1, 1, 'story', 'create'), +(1, 1, 'product', 'ajaxGetPlans'), +(1, 1, 'product', 'ajaxGetProjects'), +(1, 1, 'product', 'project'), +(1, 1, 'product', 'dynamic'), +(1, 1, 'product', 'doc'), +(1, 1, 'product', 'roadmap'), +(1, 1, 'product', 'delete'), +(1, 1, 'product', 'order'), +(1, 2, 'report', 'bugSummary'), +(1, 2, 'bug', 'customFields'), +(1, 2, 'file', 'download'), +(1, 2, 'doc', 'index'), +(1, 2, 'user', 'bug'), +(1, 3, 'build', 'ajaxGetProductBuilds'), +(1, 3, 'product', 'view'), +(1, 3, 'api', 'getModel'), +(1, 2, 'product', 'dynamic'), +(1, 4, 'todo', 'create'), +(1, 4, 'bug', 'close'), +(1, 4, 'user', 'dynamic'), +(1, 5, 'build', 'create'), +(1, 5, 'task', 'report'), +(1, 5, 'task', 'ajaxGetProjectTasks'), +(1, 5, 'task', 'ajaxGetUserTasks'), +(1, 5, 'task', 'confirmStoryChange'), +(1, 5, 'task', 'export'), +(1, 6, 'product', 'ajaxGetProjects'), +(1, 1, 'product', 'edit'), +(1, 2, 'project', 'grouptask'), +(1, 3, 'svn', 'apiSync'), +(1, 4, 'testtask', 'index'), +(1, 5, 'task', 'view'), +(1, 1, 'product', 'view'), +(1, 1, 'product', 'create'), +(1, 1, 'product', 'browse'), +(1, 1, 'product', 'index'), +(1, 1, 'todo', 'import2Today'), +(1, 1, 'todo', 'mark'), +(1, 1, 'todo', 'export'), +(1, 1, 'todo', 'delete'), +(1, 1, 'todo', 'view'), +(1, 1, 'todo', 'edit'), +(1, 2, 'testtask', 'index'), +(1, 2, 'story', 'ajaxGetProductStories'), +(1, 2, 'product', 'doc'), +(1, 2, 'project', 'view'), +(1, 2, 'bug', 'saveTemplate'), +(1, 3, 'bug', 'deleteTemplate'), +(1, 3, 'doc', 'editLib'), +(1, 4, 'bug', 'assignTo'), +(1, 4, 'project', 'testtask'), +(1, 4, 'bug', 'activate'), +(1, 4, 'project', 'task'), +(1, 4, 'story', 'ajaxGetProjectStories'), +(1, 4, 'tree', 'browse'), +(1, 4, 'story', 'export'), +(1, 5, 'task', 'delete'), +(1, 5, 'task', 'activate'), +(1, 5, 'task', 'batchClose'), +(1, 5, 'task', 'close'), +(1, 5, 'task', 'cancel'), +(1, 5, 'task', 'finish'), +(1, 5, 'task', 'start'), +(1, 5, 'task', 'assignTo'), +(1, 5, 'task', 'edit'), +(1, 6, 'doc', 'view'), +(1, 6, 'testtask', 'cases'), +(1, 1, 'todo', 'create'), +(1, 1, 'my', 'changePassword'), +(1, 1, 'my', 'editProfile'), +(1, 1, 'my', 'dynamic'), +(1, 1, 'my', 'profile'), +(1, 2, 'testtask', 'browse'), +(1, 2, 'testtask', 'view'), +(1, 2, 'testcase', 'export'), +(1, 2, 'project', 'task'), +(1, 3, 'todo', 'export'), +(1, 3, 'task', 'create'), +(1, 3, 'task', 'edit'), +(1, 4, 'file', 'download'), +(1, 4, 'tree', 'fix'), +(1, 4, 'bug', 'confirmStoryChange'), +(1, 4, 'tree', 'ajaxGetOptionMenu'), +(1, 5, 'task', 'batchCreate'), +(1, 5, 'task', 'create'), +(1, 6, 'file', 'ajaxUpload'), +(1, 6, 'misc', 'ping'), +(1, 2, 'qa', 'index'), +(1, 3, 'testcase', 'index'), +(1, 4, 'my', 'changePassword'), +(1, 6, 'release', 'ajaxGetStoriesAndBugs'), +(1, 1, 'my', 'project'), +(1, 1, 'my', 'story'), +(1, 1, 'my', 'testCase'), +(1, 1, 'my', 'testTask'), +(1, 1, 'my', 'bug'), +(1, 3, 'todo', 'import2Today'), +(1, 1, 'my', 'task'), +(1, 2, 'tree', 'browse'), +(1, 3, 'task', 'assignTo'), +(1, 4, 'bug', 'create'), +(1, 1, 'my', 'todo'), +(1, 2, 'task', 'ajaxGetProjectTasks'), +(1, 1, 'my', 'index'), +(1, 2, 'report', 'productInfo'), +(1, 4, 'doc', 'index'), +(1, 1, 'index', 'index'), +(1, 2, 'release', 'browse'), +(1, 1, 'action', 'undelete'), +(1, 1, 'mail', 'index'), +(1, 1, 'mail', 'detect'), +(1, 1, 'mail', 'edit'), +(1, 1, 'mail', 'save'), +(1, 1, 'mail', 'test'), +(1, 1, 'report', 'index'), +(1, 1, 'report', 'projectDeviation'), +(1, 1, 'report', 'productInfo'), +(1, 1, 'report', 'bugSummary'), +(1, 1, 'report', 'workload'), +(1, 1, 'admin', 'index'), +(1, 1, 'admin', 'checkDB'), +(1, 1, 'admin', 'clearData'), +(1, 2, 'project', 'ajaxGetProducts'), +(1, 2, 'tree', 'ajaxGetSonModules'), +(1, 4, 'doc', 'editLib'), +(1, 2, 'product', 'edit'), +(1, 3, 'todo', 'edit'), +(1, 4, 'report', 'projectDeviation'), +(1, 3, 'project', 'team'), +(1, 4, 'user', 'project'), +(1, 3, 'task', 'finish'), +(1, 5, 'project', 'maintainrelation'), +(1, 2, 'company', 'browse'), +(1, 4, 'project', 'burnData'), +(1, 3, 'todo', 'mark'), +(1, 4, 'product', 'roadmap'), +(1, 2, 'release', 'edit'), +(1, 2, 'testtask', 'results'), +(1, 2, 'productplan', 'view'), +(1, 2, 'project', 'index'), +(1, 2, 'build', 'ajaxGetProductBuilds'), +(1, 2, 'testtask', 'cases'), +(1, 2, 'project', 'manageProducts'), +(1, 2, 'project', 'doc'), +(1, 2, 'bug', 'browse'), +(1, 2, 'doc', 'browse'), +(1, 2, 'bug', 'deleteTemplate'), +(1, 2, 'build', 'view'), +(1, 2, 'story', 'batchCreate'), +(1, 2, 'todo', 'import2Today'), +(1, 2, 'story', 'ajaxGetProjectStories'), +(1, 2, 'my', 'index'), +(1, 2, 'company', 'dynamic'), +(1, 2, 'report', 'projectDeviation'), +(1, 2, 'product', 'index'), +(1, 2, 'product', 'browse'), +(1, 2, 'product', 'create'), +(1, 2, 'todo', 'mark'), +(1, 2, 'project', 'browse'), +(1, 2, 'story', 'export'), +(1, 2, 'release', 'export'), +(1, 2, 'productplan', 'delete'), +(1, 2, 'story', 'view'), +(1, 2, 'story', 'delete'), +(1, 2, 'productplan', 'edit'), +(1, 2, 'product', 'order'), +(1, 2, 'project', 'linkStory'), +(1, 2, 'project', 'burnData'), +(1, 2, 'my', 'todo'), +(1, 2, 'story', 'change'), +(1, 2, 'testcase', 'view'), +(1, 2, 'story', 'close'), +(1, 2, 'testcase', 'index'), +(1, 2, 'todo', 'edit'), +(1, 2, 'my', 'changePassword'), +(1, 2, 'project', 'build'), +(1, 2, 'product', 'view'), +(1, 3, 'project', 'task'), +(1, 3, 'release', 'export'), +(1, 3, 'release', 'browse'), +(1, 3, 'productplan', 'browse'), +(1, 3, 'task', 'view'), +(1, 3, 'task', 'ajaxGetUserTasks'), +(1, 3, 'my', 'index'), +(1, 3, 'user', 'profile'), +(1, 3, 'company', 'index'), +(1, 3, 'user', 'bug'), +(1, 3, 'user', 'project'), +(1, 3, 'user', 'todo'), +(1, 3, 'company', 'browse'), +(1, 3, 'user', 'view'), +(1, 3, 'user', 'task'), +(1, 3, 'company', 'dynamic'), +(1, 3, 'group', 'browse'), +(1, 3, 'bug', 'resolve'), +(1, 3, 'bug', 'customFields'), +(1, 3, 'testcase', 'view'), +(1, 3, 'project', 'bug'), +(1, 3, 'release', 'ajaxGetStoriesAndBugs'), +(1, 3, 'project', 'story'), +(1, 3, 'project', 'grouptask'), +(1, 3, 'project', 'index'), +(1, 3, 'project', 'ajaxGetProducts'), +(1, 3, 'my', 'project'), +(1, 3, 'release', 'view'), +(1, 3, 'project', 'burnData'), +(1, 3, 'project', 'burn'), +(1, 3, 'project', 'view'), +(1, 3, 'productplan', 'view'), +(1, 3, 'project', 'doc'), +(1, 3, 'project', 'dynamic'), +(1, 3, 'report', 'index'), +(1, 3, 'doc', 'view'), +(1, 3, 'doc', 'edit'), +(1, 3, 'report', 'bugSummary'), +(1, 3, 'report', 'productInfo'), +(1, 3, 'report', 'projectDeviation'), +(1, 3, 'bug', 'browse'), +(1, 3, 'bug', 'index'), +(1, 3, 'bug', 'report'), +(1, 3, 'svn', 'cat'), +(1, 3, 'svn', 'diff'), +(1, 4, 'company', 'dynamic'), +(1, 4, 'company', 'browse'), +(1, 4, 'group', 'browse'), +(1, 4, 'testcase', 'view'), +(1, 4, 'testcase', 'create'), +(1, 4, 'testcase', 'export'), +(1, 4, 'project', 'ajaxGetProducts'), +(1, 4, 'project', 'team'), +(1, 4, 'project', 'dynamic'), +(1, 4, 'testcase', 'confirmStoryChange'), +(1, 4, 'todo', 'export'), +(1, 4, 'user', 'bug'), +(1, 4, 'user', 'view'), +(1, 4, 'product', 'doc'), +(1, 4, 'product', 'index'), +(1, 4, 'product', 'browse'), +(1, 4, 'product', 'view'), +(1, 4, 'my', 'story'), +(1, 4, 'my', 'testCase'), +(1, 4, 'my', 'index'), +(1, 4, 'my', 'bug'), +(1, 4, 'my', 'editProfile'), +(1, 4, 'my', 'project'), +(1, 4, 'doc', 'delete'), +(1, 4, 'report', 'bugSummary'), +(1, 4, 'doc', 'view'), +(1, 4, 'report', 'productInfo'), +(1, 4, 'testcase', 'batchCreate'), +(1, 4, 'bug', 'ajaxGetModuleOwner'), +(1, 4, 'testtask', 'view'), +(1, 4, 'todo', 'view'), +(1, 4, 'task', 'export'), +(1, 4, 'task', 'ajaxGetUserTasks'), +(1, 4, 'task', 'view'), +(1, 4, 'task', 'ajaxGetProjectTasks'), +(1, 5, 'project', 'ajaxGetProducts'), +(1, 5, 'project', 'linkStory'), +(1, 5, 'project', 'unlinkMember'), +(1, 5, 'project', 'manageMembers'), +(1, 5, 'project', 'manageProducts'), +(1, 5, 'project', 'dynamic'), +(1, 5, 'project', 'doc'), +(1, 5, 'project', 'team'), +(1, 5, 'project', 'burnData'), +(1, 5, 'project', 'computeBurn'), +(1, 5, 'project', 'burn'), +(1, 5, 'project', 'bug'), +(1, 5, 'project', 'testtask'), +(1, 5, 'project', 'build'), +(1, 5, 'project', 'story'), +(1, 5, 'project', 'importBug'), +(1, 5, 'project', 'importtask'), +(1, 5, 'project', 'grouptask'), +(1, 5, 'project', 'task'), +(1, 5, 'project', 'delete'), +(1, 5, 'project', 'order'), +(1, 5, 'project', 'edit'), +(1, 5, 'project', 'create'), +(1, 5, 'project', 'browse'), +(1, 5, 'project', 'view'), +(1, 5, 'project', 'index'), +(1, 5, 'release', 'export'), +(1, 5, 'release', 'ajaxGetStoriesAndBugs'), +(1, 5, 'release', 'view'), +(1, 5, 'release', 'browse'), +(1, 5, 'productplan', 'view'), +(1, 5, 'productplan', 'browse'), +(1, 5, 'story', 'ajaxGetProductStories'), +(1, 5, 'story', 'ajaxGetProjectStories'), +(1, 5, 'story', 'report'), +(1, 5, 'story', 'tasks'), +(1, 5, 'story', 'view'), +(1, 5, 'story', 'export'), +(1, 5, 'product', 'ajaxGetPlans'), +(1, 5, 'product', 'ajaxGetProjects'), +(1, 5, 'product', 'project'), +(1, 5, 'product', 'dynamic'), +(1, 5, 'product', 'doc'), +(1, 5, 'product', 'roadmap'), +(1, 5, 'product', 'view'), +(1, 5, 'product', 'browse'), +(1, 5, 'product', 'index'), +(1, 5, 'todo', 'import2Today'), +(1, 5, 'todo', 'mark'), +(1, 5, 'todo', 'export'), +(1, 5, 'todo', 'delete'), +(1, 5, 'todo', 'view'), +(1, 5, 'todo', 'edit'), +(1, 5, 'todo', 'create'), +(1, 5, 'my', 'changePassword'), +(1, 5, 'my', 'editProfile'), +(1, 5, 'my', 'dynamic'), +(1, 5, 'my', 'profile'), +(1, 5, 'my', 'project'), +(1, 5, 'my', 'story'), +(1, 5, 'my', 'testCase'), +(1, 5, 'my', 'testTask'), +(1, 5, 'my', 'bug'), +(1, 5, 'my', 'task'), +(1, 5, 'my', 'todo'), +(1, 5, 'my', 'index'), +(1, 1, 'todo', 'batchCreate'), +(1, 2, 'todo', 'batchCreate'), +(1, 3, 'todo', 'batchCreate'), +(1, 4, 'todo', 'batchCreate'), +(1, 5, 'todo', 'batchCreate'), +(1, 5, 'index', 'index'); diff --git a/trunk/doc/CHANGELOG b/trunk/doc/CHANGELOG new file mode 100644 index 0000000000..5d1b7a1917 --- /dev/null +++ b/trunk/doc/CHANGELOG @@ -0,0 +1,844 @@ +2012-9-3 3.3发布 + +需求: + +447 完善详情页面的标题处理 +541 优化sql查询 +544 创建发布的时候,build可以为空 +555 用户可以自动添加附件上传框 +691 优化前端性能 +692 压缩css,js文件 +693 合并样式表中用到的图片文件 +711 安装禅道的时候,提供语言的选择 +712 在生成model的缓存文件的时候,判断tmp/model是否存在可写 +715 导入任务的时候,增加对项目的筛选 +719 调整权限分组的用户管理,以适应用户很多的情况 +747 调整变更需求时影响范围的排版 +749 调整组织视图用户todo的界面 +751 添加todo的时候,增加对暂不指定的处理 +752 featurebar的字体还是调成大字体 +753 调整图标样式 +754 调整产品下拉菜单 +755 批量添加todo的时候,自动计算时间间隔 +756 调整批量添加todo时候日期选择框的样式 +757 当图片太大的时候,自动增加链接,点击可以查看完整尺寸的图 +760 调整附件下载代码的逻辑 +761 如果没有扩展名的附件,不要增加扩展名 +763 处理完善测试数据的权限 + + 安装的时候改为检查数据表是否存在。 + 优化性能 + 调整组织视图用户todo页面。 + 汉化ioncube load wizard. + 调整todo的批量添加批量编辑页面。 + 调整任务的批量添加和批量编辑页面。 + 重新组织项目视图的菜单列表。 + 调整统计报表页面的内容和样式。 + +bug: + +345 计划的开始和结束日期没有比较 +344 删除用户后,权限分组页面仍可分配权限 +343 项目创建项目起止时间会有提示“请填写合法的日期” +339 testtask模块create.html.php没有考虑值为空的情况 +338 testcase模块的create.html.php错误 +337 批量添加todo之后跳转的页面参数不对 +336 选中某一个模块之后,点击创建bug链接,没有把模块字段带过来 +334 批量添加todo的时候,todo标签没有高亮 +333 report模块的model.php的getProject函数没有考虑值为空的情况 +321 统计视图版面乱码 +317 添加项目出现是否停止运行此脚本的对话框 +315 在IE8下编辑bug时,选择所属项目后,影响版本列表没有反应。 +307 测试用例列表,用例标题没有悬停提示完整标题的功能。 + +2012-7-16 3.2.1 发布 + +调整: + 修复首页滚动条问题。 + 修复任务不能批量关闭的问题。 + 修复bug列表上一个,下一个不对的问题。 + 调整id字段的宽度。 + 任务增加模块的编辑和查看。 + 将纯图标的链接形式换成图标和文字组合的形式。 + 调整初次创建任务再编辑时提示状态必须的完成的逻辑。 + 修复产品的下拉列表没有按order字段排序的问题。 + 调整统计视图报表的一些字段错误。 + +2012-7-3 3.2 发布 + +框架: + +框架的扩展增加model的钩子脚本支持。 +框架的扩展增加对加密软件的支持。 +dao:: 增加ge方法。 +beginIF增加对orderby的支持。 +增加数据库配置扩展到功能。 + +界面: +调整操作界面,增加小图标。 + +新增功能: +633 在详情页面,增加上一个和下一个的链接 +727 优化产品和项目的下拉菜单选择 +763 为任务增加开始字段 +777 解决svn中文路径的问题 +779 完善项目的详情页面 +780 完善计划管理的详情页面 +781 增加新的todo选择标签 +782 按模块搜索增加对下级模块的支持 +784 完善发布的详情页面 +785 测试任务的用例页面增加用例数。 +786 完善build的详情页面 +788 统一所有的条目统计信息的位置,统一放在左侧 +789 将下拉菜单的todo日期选择框改为日期选择控件 +790 我的地盘里面的任务,bug,需求增加分页 +791 批量添加todo的时候,应该可以选择日期。 +792 项目关联需求的时候,增加搜索功能 +793 将修改密码的功能独立出来 +794 发信的时候,将对象的详细描述带上 +795 按照日期进行搜索的时候,考虑将文本框改为日期选择框 +796 build和发布在关联需求和bug的时候,增加全选和取消全选的按钮 +797 浏览todo的时候,增加昨日安排的选项菜单 +798 bug增加指派操作 +799 调整bug需求变动页面的排版 +800 测试结果的页面,增加版本的显示 +801 添加用户的时候,支持用户名带. +802 部署html5的placeholder属性 +803 增加对需求的批量操作 +805 实现任务列表页面的批量操作 +808 提供demo数据 +809 demo环境提供不同的演示帐号 +814 测试任务的用例列表页面增加分页功能 +818 优化email配置功能 +819 bug统计图表,可否增加按“build”统计功能。 +820 首页的的项目和产品表格表头固定 +821 添加项目的时候,完善时间处理 +822 创建发布和build时,隐藏trunk,增加新增build链接 +825 调整任务添加的逻辑,取消最初预计和优先级字段的必填属性 +848 调整需求、任务、bug下拉菜单检索 +849 调整测试用例列表页面 +854 任务管理增加模块功能 +856 bug转需求之后,将bug的提交者放在需求的抄送列表中 +857 增加对导航菜单显示顺序的定义 +859 导出发布的修改记录为html代码 +860 调整build和发布关联需求和bug时候的逻辑 +861 svn命令参数增加--trust-server-cert选项 +863 完成测试任务所对应的测试用例的导出功能 +864 超级管理员修改自己的用户名的时候,同时修改数据库 +866 windows一键安装包打包的时候加载openssl的支持 +867 创建测试任务的时候,根据所选择的项目筛选build列表 + +调整搜索表单的检索顺序。 +任务增加开始日期字段。 +移除产品和项目下拉菜单的键盘扩展。 +移除帮助的?链接。 +调整导航菜单。 + +BUG: +313 搜索任务的时候,已经过期的任务没有提示 +310 删除用户之后,在bug列表页面没有显示相应的用户名 +306 当系统存在n多模块时,编辑模块后保存时界面会等待很久。 +298 编辑项目的时候,项目负责人,测试负责人,发布负责人有问题 +297 项目视图->产品页显示乱码 +296 chrome下面创建发布的时候,保存按钮被遮掩 +291 创建需求的时候添加附件排版问题 +290 用例浏览添加备注没有“保存”“重填”按钮 +289 bug查看页面左上角链接排版问题 +287 英文的action应当改为小写 +286 英文界面下面模块维护的链接显示不妥 +284 批量添加需求界面,“需求标题”与“需求名称”用词不一致。 +138 缺陷列表,如“自定义字段”设定显示“影响版本”,则显示的是 id号而非版本号名称。 + +2012-5-3 3.1发布 + +STORY: +687 windows集成环境关闭innodb +681 提升集成运行环境的性能,增加expires模块。 +506 重构checkall。 +621 添加todo的时候,日期改为日历控件 +644 实现备份脚本 +650 可以批量添加todo列表 +659 恢复之前的需求添加页面 +661 build和发布增加完成需求,解决bug的数字统计 +665 完善bug自定义列表页面build的显示 +667 升级的时候,判断是否存在my.php +668 将待办事宜里面的日期选择改用日历选择框 +674 重新指派任务的时候,应当增加预计剩余字段 +675 编辑任务的时候,将由谁完成改为项目的团队成员,增加首字母的提示 +676 在涉及到工时的字段的时候,在后面增加单位的提示 +677 导出csv的时候,增加导出编码的选项 +678 完善需求的研发阶段处理逻辑 +679 项目关联需求的时候,增加全选按钮 +684 首页的产品信息中增加bug的统计 +685 集成运行环境中集成数据库检查修复脚本 +686 调整安装完成之后的登录按钮 +688 调整build的概念,将其改为版本 +689 完善项目的工时管理 + +修改登录页面的样式。 +取消添加和编辑用户时账户和密码的自动完成功能。 +调整变更时验证字段的diff显示样式。 +调整测试用户的用例浏览和关联用例的页面。 +windows一键安装包集成控制面板的源代码。 +备注功能增加锚点,确保页面焦点在备注框里面。 + +BUG +309 任务搜索,选择最后编辑没有出现预期结果 +304 Bug转任务之后,点击bug详情页面的转任务应当跳到对应的任务详情页面 +302 取消了用户的动态访问权限,但在首页仍然能够列出 +301 给需求添加备注的时候,会置空需求所属的计划 +300 在维护模块的时候,如果从产品视图导入的模块过多,会导致保存按钮无法显示 +299 项目视图中的测试申请列表链接不对 +293 bug详情页面的操作按钮出现换行 +292 没有本地上传安装插件权限的用户也能看到本地上传安装的链接 +288 1024 x 768下面用例列表页面无法使用 +285 项目视图的菜单在英文界面下面显示有误 + +修复了需求变更时影响项目和任务无法列出的bug。 +修复编辑bug时图片产生修改记录的bug。 +修复变更含有图片的需求时,没有做任何修改但版本增加的bug。 +修复没有权限但可以看到批量关闭任务表单的bug。 +修复没有分配用例的权限,但可以看到指派用例的权限。 + +2012-3-3 3.0.stable发布 + +全新的模版风格。 +一键安装包增加对ioncube的支持。 +windows一键安装包提供服务管理的脚本。 +需求创建的时候,增加一个是否继续添加需求的选项,以方便用户快速添加需求。 +完善一键安装包的首页。 +保留xampp原来的授权文件。 +调整首页项目百分比的显示。 +修复用例前置条件显示的错误。 + +2012-2-23 3.0.beta2发布 + +STORY: +630 已安装的插件可以查看插件的详细信息 +634 dao增加findInSet()方法 +494 完善产品管理的详情页面 +643 修改检查更新的逻辑 +628 导出csv格式的时候,将html代码去掉 +638 不包含功能没有放在搜索表单里面 +633 将所有的private, protected改为public +489 提供复制项目的功能 +642 调整系统首页列表的显示,改为滚动显示 +623 实现插件的升级功能 +637 导入bug时搜索表单默认显示 +632 调整程序里面的测试任务的概念 +636 处理动态信息中项目,任务所属的产品信息 +645 todo标签增加暂不指定的标签 +635 更新代码中的版权提示为2012 +646 重新实现windows一键安装包 +648 系统安装完毕之后,提醒用户登记注册 +253 增加对项目排序功能 +252 增加对产品的排序功能 +649 显示安装之后插件所包含的目录和文件列表 +将系统中所有的“feature”改为“future” +在文档视图中的产品文档库和项目文档库中的“维护模块”改为“分类” +审核需求的时候,改用滚动显示。 + +BUG: +280 通过编辑操作关闭某一个转为任务的bug时,有空白alert框出来 +修复bug列表查询没有加删除逻辑的bug +修复私人todo首页显示内容的bug +修复sendmail中的bug +修复http://www.zentao.net/thread-view-80940.html 反映的代码的问题 +参考http://www.zentao.net/thread-view-80933.html,完善英文界面的翻译 + +2012-1-04 3.0.beta1发布 + +STORY: +591 完善插件打包规范 +532 创建发布的时候,自动关联完成的需求和修复的bug +609 完善任务和bug联动时的交互提示 +531 创建build的时候,关联完成的需求和解决的bug。 +508 从项目中移除需求的时候,自动取消其相应的任务 +597 bug浏览增加未关闭标签 +533 当测试任务关联用例的时候,自动根据其关联的需求和bug,确定用例 +612 调整bug转需求的功能 +463 增加产品列表页面 +590 处理禅道版本的显示 +617 创建linux下面的一键安装包 +603 svn 增加用户名对照功能 +605 完善svn命令同步的处理方式 +600 完善需求来源字段 +604 修改用户表中gendar字段的错误 +613 bug详情页面增加转化之后的需求和任务的链接 +599 完善需求研发阶段的处理 +608 需求搜索时增加按照模块搜索功能 +589 导入bug的时候,增加搜索功能 +616 将项目排序字段独立成配置 +584 组织视图的动态增加搜索功能 +615 将产品排序字段独立成配置 +602 处理svn 日志编码转化的问题 +611 完善目录创建功能 +606 增加快速评论功能 +601 需求详情页面增加来源bug链接 +610 修复autocomplete的bug + +BUG: +279 Bug转需求后,Bug关联的多个附件只剩下一个! +277 复制需求的时候,没有就来源字段复制过来 +276 导入bug的时候,在输入预计工时的时候,会把前面选中的checkbox取消 +275 bug搜索时,totask > 0不起作用 +274 按照所属产品 = 所有产品,能够搜出自己没有权限的需求 +273 管理团队成员时,已加入的团队成员名显示登录帐号而非姓名。 +261 当模块测试负责人为空的时候,应当保持产品的测试负责人 + +2011-12-07 2.4 发布 +STORY: + +596 导入bug页面增加bug的预览功能 +595 导入bug之后,刷新导入bug页面 +594 增加从项目创建需求的链接 +593 完善任务和相关bug的状态联动 +592 实现bug转需求功能 +588 测试任务关联用例的时候,增加分页功能 +587 增加产品项目列表功能 +585 执行用例的时候,记录所有的步骤结果 +527 在创建用例的时候,增加需求预览的功能 +526 在需求列表和详情页面添加创建用例链接 +512 需求详情页面增加相关用例和相关bug的显示 +490 提供自动计算项目可用工时的功能 +473 任务管理增加指派功能 +431 导出页面提示“输入文件名” +324 todo增加导出csv的功能 +320 测试执行结果应该记录执行人 +319 在项目视图中增加测试任务功能 +313 todo的历史修改记录里面显示用户的真实姓名 +312 当创建需求的时候,只选择没有过期的计划 + 增加项目可用天数的计算。 + 完善移动平台的操作系统和浏览器。 + 增加了对主配置文件的扩展功能。 + 增加对项目结束日期的检查。 + +BUG: +272 安装禅道的时候,如果php环境没有安装json扩展,页面空白 +271 进行中的项目列表为空时,项目列表页面显示逻辑不对 +269 插件管理的时候,安装补丁之后,显示的记录不对 +266 email配置gmail的时候有误 +262 编辑附件的时候,应当默认将附件的名称作为默认值 +260 创建bug时,如果重新筛选项目,原来选中的影响版本会被取消选中 +247 安装的时候,如果不设置前缀,安装会出错 +229 当用户没有设置email地址,且打开email功能的时候,会提示至少提供一个收件人 +223 批量指派任务的时候,附件存储有问题 +213 创建bug时,影响版本和所属项目 选择位置调整导致操作不便。 +203 任务导入后的状态显示 +202 无法将私人事务的todo改为非私人事务 +198 对于多个影响版本的bug,重新激活后则影响版本变为仅仅一个选择了的版本 +192 当切换语言或者风格的时候,如果url中含有#,则页面不刷新 +188 发布列表未按发布日期排序,编辑发布界面的发布列表也如此。 +187 燃尽图更新帮助链接错误 +119 我的地盘板块,新增“我的Todo”,日期选择“暂不指定”,保存之后,该Todo展示页面时间显示“2030-01-01” +113 产品视图设置查询条件之后,按照某字段进行排序无效 +019 如果php设置为session_autostart,会有警告信息 + 修复用例复制链接失效的bug。 + 修复需求来源无法编辑的bug。 + +2011-10-31 2.3 发布 +story: +453 安装的时候,增加对协议的确认 +577 需求列表页面增加各种标签筛选 +517 完善bug的处理流程,增加确认和重新激活状态 +564 用例增加前置条件功能 +580 增加需求的批量添加功能 +566 需求增加来源字段 +582 实现测试用例批量添加功能 +471 针对不同分辨率的浏览器进行优化 +568 svn增加diff功能 +558 将邮件配置插件放入到后台管理中 +446 增加项目动态功能 +576 发信的时候,将当前操作者从发信列表中去掉 +563 用例单独执行功能 +454 实现用户自定义安装和卸载功能的支持 +579 插件如果未定义是否和当前版本兼容,列表中改为未知 +518 优化产品和项目的下拉菜单选择 +464 完善项目视图的首页显示 +565 用例可以只撰写标题 +581 实现批量添加任务功能 +567 增加一个完全独立的svn同步脚本 +583 bug可以转为测试用例 +445 增加产品动态功能 +482 增加导入bug功能 +570 完善windows一键安装包的打包程序 +562 测试用例增加导入功能 +463 完善产品视图首页的显示 + +导入css,js文件时,加入时间戳 +api.class.php 增加post()方法 +后台管理调整菜单顺序。 +移除table2csv +调整英文界面ui +升级jquery 1.5.2 +升级colorbox 1.3.18 +删除不用的控件。 +都使用minjs。 +菜单条目增加id +调整插件页面。 + +bug: +265 多次修改和执行用例后,不能正确查看用例 +259 早上8点40左右添加todo,起止时间显示的列表不对 + +2011-09-22 2.2 发布 +story: + +480 发送bug相关的邮件时,将操作者替换成真实的姓名 +515 将编辑器上传中页面中的本地上传改为默认 +538 使用jquery的图表插件来实现统计报表 +525 新增任务的时候,需求预览使用colorbox打开 +553 集成环境中,增加说明文件 +548 彻底解决centos和rhel下面的问题 +552 修复dept和tree模块中模块名称的安全漏洞 +557 完善详情页面中图片的自动缩放功能 +551 增加和svn集成的功能 +546 完善默认的权限划分 +559 修复隐藏域删除操作引起的死循环 +438 修改.htaccess文件,修改php的默认配置 +524 完善产品和项目的权限控制 +550 完善编辑器上传图片返回的错误提示 +474 下载附件的时候自动追加session信息 +545 增加附件名称编辑功能 +554 每页显示的记录数记到cookie中 +549 任务未开始之前修改预计工时,应当将剩余时间也一块修改了 +352 使用不同的颜色来区分状态 +556 task模块中关于搜索的配置应当去掉 +436 common模块下面的视图文件,使用css::import和js::import来加载相应的css文件和js文件 +547 my.php被加载两次,需要优化 + 需求列表,任务列表排序记录到cookie中。 + 增加新的模板, thanks silver, www.yanhonglu.com + +bug: + +258 第一次创建任务并指派的时候,assignedDate为0. +257 创建bug时所选择的build在编辑的时候丢失 +255 解决bug的时候,没有检查解决版本是否必填 +254 导出任务列表时,指派日期显示的是人名 +253 关闭任务之后,延期天数计算不对 +249 维护模块中,超过十个汉字的模块,无法修改其名称和负责人。 +244 如果附件文件名中含有空格,下载的时候文件名会被截断 +243 使用opera导出内容时有错误 +242 点击“用户列表”中的某一用户真实姓名出现错误链接 +241 文档列表中,创建者应为真实姓名而非用户帐号。 +240 访问时我的地盘中“我的测试”不高亮显示 +235 关联产品的时候,无法取消最后一个产品的关联 +230 创建bug时,已经删除的项目还会出现 +228 5.1版本安装会失败 +227 已经结束的项目,概况统计中的工时不对 +226 测试任务中关联的某一用例如果被修改之后,无法执行 +224 删除todo的时候,发生页面死循环 +215 搜索不能保存 + +2011-07-06 2.1发布 + +story: +359 1 更新代码里面的copyright时间 +462 1 完善我的地盘首页的显示 +452 1 提供在线扩展功能 +456 1 获取插件信息的时候,增加语言选项 +461 1 完善系统首页的展示 +455 2 增加对插件类型的判断 +529 2 完成redmine的导入插件 +469 2 完善对ie9的支持 +535 2 调整每日提醒的插件,发布到我们的插件平台 +457 2 实现插件升级功能 +509 2 完善phpmailer的错误提示 +534 2 完成zentaotest的插件调整 +516 2 增加对page css和page js扩展的支持 +466 3 将QA视图改为测试视图 +524 3 完善产品和项目的权限控制 +468 3 升级kindeditor编辑器 +383 3 自动配置sae版本的邮件发信功能 +479 3 在检查插件的md5值的时候,需要处理没有md5值的情况 +536 3 完善bug自定义字段的列表功能 + +bug: + +237 3 导入信息不完整返回重新填写不能保存 +236 2 变更需求时,不修改需求内容则无法保存验收标准的内容。 +235 3 关联产品的时候,无法取消最后一个产品的关联 +234 3 浏览任务的时候,切换项目,会提示对象不存在 +233 3 英文界面下面创建Bug,模板为空 +232 3 状态为doing的todo,其后面的操作代码doing应当改成done +228 3 5.1版本安装会失败 +226 3 测试任务中关联的某一用例如果被修改之后,无法执行 +225 3 编辑公司之后,无法继续保存 +222 4 编辑bug时,影响版本列出的是所有,而非仅仅是所属项目的build/发布 +221 3 按照模块维护分组,我的的地盘中的测试任务,测试用例,分给某一个分组之后,在分组权限中无法看到。 +219 3 在创建bug的时候,会有错误日志,提示bugOwner字段不存在 +218 3 项目视图中任务无法按照需求进行分组 +217 3 组织视图中私人事务的todo也被显示了出来 +215 3 搜索不能保存 +214 3 编辑bug页面,修改“所属产品”时,弹出js提示,内容为php代码。 +212 3 build无法编辑 +209 3 用户动态的“操作者”列的内容不正确。 +208 3 组织动态的“操作者”列的内容不正确。 + +2011-05-03 2.0发布 + +story: + +398 确定插件打包的规范要求 +399 实现插件管理 +355 添加记录的时候,调整字段顺序 +382 自动提供sae版本的计划任务 +384 增加动态历史一个栏目 +385 我的地盘中增加我的动态 +386 组织视图中,某一个用户的状态页面,增加动态历史 +396 实现需求的基本统计报表 +397 实现任务的基本报表统计 +413 为每个模块建立初始的ext扩展目录。 +422 优先级的默认设定 +184 需求增加如何演示字段 +376 点击表格的一行,应当可以选中这一行中的checkbox +378 处理[]这样的标签,加以区分 +380 完善sae版本的安装程序 +387 需求增加复制功能 +388 测试用例增加复制功能 +389 一个产品的模块可以从其他产品复制过来 +390 common模块的footer.html.php文件增加hook的支持 +391 完善路线图的排序 +395 需求搜索增加按照产品进行搜索 +405 项目视图中的需求列表改为服务器端排序 +406 在分解任务的时候,将需求的优先级和预计工时也都列出来。 +408 在产品的需求列表和计划的需求列表页面增加所有需求的预计工时的总计 +409 在任务列表中增加工时的统计信息 +410 任务列表增加已关闭浏览选项 +411 将所有的分页显示代码都包在表格中 +412 点击“搜索”,实现搜索表单的切换显示 +418 将导出功能改为php实现 +419 首页的动态增加更多链接 +420 整理每个版本新增的权限 +424 调整变更需求时的界面 +392 发送邮件的时候,自动加上产品或者项目的名称 +393 文档增加备注功能 +407 增加分页显示的数量选择 +423 预计工时的默认显示 + +bug: + +197 使用管理员为某一个帐号分配权限,然后使用这个用户登录,提示没有权限 +196 使用IE第一次访问禅道,页面显示英文,但语言提示显示中文简体 +195 搜索任务时,搜索所有项目的任务,没有分页 +194 导入之前取消的一个任务时,应当将由谁取消和取消时间置空 +193 取消登录界面的“请稍候”功能 +190 修改或创建发布名称为已有的发布时,无任何反应,无任何提示。 +189 修改build名称为已有build时,无任何反应,无任何提示。 +175 Build列表未按Build日期排序,编辑bug界面的build列表也如此。 + +2011-3-15 1.5 发布 + +story: +361 修改opt目录为ext +372 为任务列表增加搜索功能 +363 将字段提示纳入到发行版本中 +365 完善项目关联需求时的提示 +358 在模块下面增加css和js的概念 +367 用户可以修改个人的其他信息 +362 修复todo里面导入到今天的错误 +373 为文档模块增加搜索功能 +364 完善首页的提示 +366 燃尽图增加提示 +134 可以对任务按照各种条件进行检索 +360 更改所有的private方法为protected +371 附件表单权限的提示,应当放入到语言文件中 +354 安装的时候自动设定默认语言 +368 安装的时候检查数据库是否存在 +324 todo增加导出csv的功能 +369 调整build表描述字段的长度 +328 增加将权限分配到多个分组的功能 + 我的地盘中任务批量关闭 + 完善燃尽图的样式 + 完善用户分组的样式 + 调整todo的时间区域 + 增强upgrade.php的安全 + +fix bug: +185 bug编辑界面,系统列表中没有win2003和mac os。 +184 使用jsonapi获取bug列表,出现错误 +183 用户名中如果包含.,无法移除 +181 当一个产品被删除之后,该产品相关的bug还会出现在我的地盘 +180 任务分组的treetable在firefox下面无法折叠 +179 如果某一个任务被取消,然后被关闭,仍然被计算到项目的剩余时间中 +178 编辑需求时,如果切换产品,无法加载新产品的模块 +177 项目列表当所有的项目都结束之后,项目列表就不再显示了 +176 centos下面的pdo错误 +175 Build列表未按Build日期排序,编辑bug界面的build列表也如此。 +174 复制bug后,“类型/严重程度”选项变成默认,并非源bug的类型。 +173 windows一键安装包访问权限漏洞 +171 测试任务关联用例页面“保存”按钮显示为“搜索” +163 通过完成、取消等按钮操作任务时,起对应的需求的所处阶段没有修改 +145 编辑需求保存后,如所属模块选为“/”,则保存后,显示为“”。 +139 组织视图的用户信息显示,未显示所属部门。 +119 我的地盘板块,新增“我的Todo”,日期选择“暂不指定”,保存之后,该Todo展示页面时间显示“2030-01-01” + 附件不存在时,逻辑不严谨 + 修复account检查的错误 + 修复分组权限不能清空的bug + 修复分组用户不能清空的bug + 关闭任务时,邮件的提示。 + +story: +051 我的档案中完善其他字段 +162 添加完项目之后,提示用户该如何处理 +177 分组用户选择增加全选功能 +229 项目增加项目负责人、产品负责人、测试负责人、发布负责人 +260 Bug, task, case编辑页面需求下拉菜单优化 +286 增加多风格支持 +287 整理代码,重新调整一个默认的风格 +290 完善英语版本 +309 调整燃尽图更新程序computeburn.php的实现方式 +310 调整代码,改用英文注释 +311 暂不指定的todo,也可以导入到今天 +312 当创建需求的时候,只选择没有过期的计划 +313 todo的历史修改记录里面显示用户的真实姓名 +314 当暂不指定的todo完成之后,将日期改为当前的日期 +318 剩余模块增加编辑器 +321 super对象增加结构打印方法 +323 优化分组中成员维护的页面 +325 bug增加按照指派给进行统计 +326 附件显示的时候,增加扩展名的显示 +327 测试任务增加负责人功能 +330 增加的每个版本新增权限的提示 +331 我的任务细分为由我创建和指派给我两个选项卡 +332 我的bug里面细分为由我创建,指派给我,由我解决的bug +333 我的需求,细分为由我创建,由我负责,和由我评审 +334 安装的时候,提供一个默认的guest分组 +335 优化对chrome, safari的支持 +337 我的地盘增加“我的测试”一栏 +339 项目文档库中调整显示,按照产品组织项目 +343 默认将diff信息隐藏 +344 通过配置文件来设置todo的起止时间 +345 视图文件的钩子扩展可以是多个 +346 model扩展改为ext调用 +347 项目列表默认不显示已经结束的项目 +349 任务完善流程处理字段 + +bug: +170 如果某一个用户已经被删除,在维护QA视图页面还会出现 +168 使用opera编辑器或者linux下面的firefox进行编辑,会出现内容丢失的现象 +167 如果某一个用户已经被删除,在权限维护页面还会出现 +166 文档查看页面的标题,应当改为文档查看 +165 不同浏览器编辑同一个文档,空格和 互相转换的问题 +164 上传附件如果没有权限写入,应当给予提示 +162 转换bugfree,没有执行nl2br +161 bugfree2.0.3里面用户组的表名为testusergroup +159 当上传的附件扩展为为大写的PNG, JPG的时候,点击附件没有在浏览器直接打开 +157 英文导出csv,没有空格 +156 首页统计中的产品、项目、需求、任务、bug等应该去掉已删除的记录 +155 创建发布时,build列表列出的内容包括了“发布”。 +154 维护子模块的复制产品视图模块功能错误 +153 删除文档提示无提示内容。 +152 QA视图-创建bug:没有将产品bug负责人填写到“当前指派”里 +150 编辑bug时,如重选所属项目,则“解决版本”自动变为 trunk,保存后也如此显示。 +149 编辑build时,“指派给”自动变成当前修改者。 +140 各界面数据的编辑功能中,保存按钮应在点击后禁用,以防止连续点击导致重复提交。 +137 更改用户名后,新用户名无法登录 +135 build列表中,构建者显示为登录名而非真实姓名 +124 产品视图:新增产品时,选中“只有项目团队成员才能访问”,提交时遇到“您无权访问该产品”错误 +97 BugFree2转换时TestGroup表不存在 + +2010-09-15 1.3版本发布 + +story: +259 开发禅道api客户端的php实现 +081 完善开发者指南 +291 完善文档功能,启用未用字段 +265 修改邮件错误提示为相应的语言文件 +202 优化历史记录的显示 +283 文档下载功能 +293 增加附件删除功能 +179 首页显示内容优化 +292 其他模块增加富文本编辑器功能 + +bug: +141 QA视图-用例管理-搜索-重置: 点击重置无法将输入框的搜索条件完全清除 +134 bug视图模块及用例视图中,从产品视图复制功能无效 +133 浏览bug时,久未处理的查询逻辑不对 +132 1.2版本无法从bugfree转换 +131 当产品视图的模块中含有空格的时候,复制到bug模块中,空格后面的部分被移除 +130 文档库中文档名称的重复检查应该在同一个模块下面 +129 表单字段中,如果含有sql关键词,会导致报错 +126 产品视图的文档列表中项目文档没有列出其类型 +125 删除文档应该给予提示 +123 QA视图:创建bug时选择了“项目任务”,导致影响版本只能选择“Trunk” +122 编辑bug时,默认没有选择已设置过的影响版本 +121 测试任务关联用例的时候,选择用例版本无效 +116 发送邮件时的链接没有将端口号写上 +114 新增需求的时候,发送邮件只有标题,没有描述 + +2010-08-03 1.2 发布 + +007 Bug视图中的模块可以从产品视图复制 +041 产品设置访问的白名单 +050 bug的模块可以指定默认的指派人 +144 任务增加关闭操作 +194 QA视图BUG列表增加自定义字段显示 +215 增加英语语言的支持 +263 todo的起止时间可以通过配置文件来进行设定 +267 汉化集成安装环境 +269 处理zentao环境中的.htaccss文件 +270 调整集成安装环境php的报错信息 +271 增加中文繁体支持 +273 增加我的模板功能 +274 增加复制bug功能 +275 用例增加附件功能 +276 增加富文本编辑器功能 +277 文档库管理功能 + +2010-07-06 1.1 发布 + +完成的功能列表: + story# 255 json接口增加状态码和签名验证 + story# 258 支持$account:$password@pms方式的验证 + story# 257 用户验证支持加密验证 + story# 256 用户验证支持get方式 + story# 254 增加禅道运行系统配置的接口 + story# 241 批量建立模块的opt目录 + story# 240 转换程序代码格式为unix格式 + story# 261 完善软件包里面的changelog等文件 + story# 214 增加common模块扩展的功能 + story# 250 统一调整视图赋值方式 + story# 249 将dao中的oncaseof改为beginIF + + story# 147 增加保存查询功能 + story# 239 搜索查询表单增加重置按钮 + + story# 251 新建资源时,不列出已经删除的资源 + story# 234 记住用户上次选择的产品 + story# 235 记住用户上次选择的项目 + story# 136 任务相应的变化email通知相应人员 + story# 236 未经审核的需求再次变更时,不应当修改指派给字段 + story# 228 bug统计报表增加全选和反选按钮 + story# 178 新增任务时,可以指派给多人 + +修复的bug列表: + bug# 118 当一个模块含有model扩展时,修改主体的model.php无效 + bug# 117 bin/computeburn.php包含snoopy类时,应当使用绝对路径 + bug# 115 如果某一个模块的model.php最后一行是?>;,并且有扩展,会出错 + bug# 112 我的地盘中,“之前未完”的TodoID错位 + bug# 100 win7下面IE8页面滚动条出现故障 + bug# 96 变更需求页面,选择“由谁评审”,在需求展示页面对应的是“指派给” + bug# 92 为分组选择成员时,备选列表中不应该出现已经被删除的用户 + bug# 91 创建Bug时,“当前指派”列表中出现了已经被删除的用户 + +2010-06-04 1.0.1 发布 + + 本期修复的bug如下: + 85 添加新用户时,“加入日期”无法选择2009年5月份以前的日期 + 87 分解任务之后,无法在项目任务列表中展示 + 88 虚拟主机上面禁用glob函数之后,框架报错 + 89 创建任务是,点击“同需求”按钮,会把需求id带入。 + 90 当build重复添加的时候,系统没有提示 + 93 存在安全漏洞(跨站脚本、未加密的传输)详见附件 + 94 任务已经结束,但仍然显示延期 + 95 创建需求时,没有发送email给审核者 + 101 安装时访问更新api,超时会有警告 + 102 需要确认需求变更的bug,如果标题太长,确认链接无法显示 + 103 当某一个项目的燃烧图数据超过21天时,数据显示有误 + 104 用户详情页面不应该显示昵称字段 + 105 common模块根据HTTP_HOST变量查找公司是有误 + 107 计算燃尽图数据时,将已经删除的任务也计算在内了 + 108 如果bug标题中有英文的单引号,编辑该bug,英文单引号后面的字符全部消失 + 110 项目视图中的任务列表,按照需求分组的时候,会有waring信息 + +2010-05-03 1.0 正式版发布 + + 修复Bug + 正式推出全新的操作界面。 + 若干小功能的改进。 + +2010-04-28 1.0 rc2 发布。 + + 本期改进主要集中在界面方面,同时修改了很多的bug + +2010-04-19 1.0 rc1 发布。 + + 完善了删除机制。 + 完善了多公司的支持。 + 完善了界面的UI。 + 增加了列表的导出csv功能。 + +2010-04-01 1.0 beta发布。 + + 完善了禅道软件的扩展机制。文档请参考:http://www.zentao.cn/node78847.html + 完善了需求变更处理流程。 + 完成了bugfree2的转换程序。 + 需求,bug,用例增加了关键词字段。 + 项目增加了权限设置。 + 其他若干地方改进和bug fix。 + +2010-03-10 0.6 beta发布。 + + STORY #146 搜索时可以指定空值 + STORY #140 当维护某一个模块的子模块时,可以在中间插入模块并自动计算顺序 + STORY #127 创建bug时,影响版本可以选择多个 + STORY #148 实现bugfree2X版本的BUG图表统计 + STORY #142 任务列表增加分组展示功能 + STORY #098 日期字段使用js的控件来选取 + STORY #047 todo和bug之间的联动 + STORY #139 产品视图增加模块删除功能 + STORY #085 新建任务的时候,完善用户列表 + STORY #046 todo和项目任务状态的联动 + STORY #157 必填项给予说明 + STORY #143 任务增加附件功能 + STORY #138 产品视图增加模块编辑功能 + STORY #111 任务增加起止时间字段 + STORY #080 display方法提供直接转换为xml或者json的功能 + STORY #159 新增任务的时候,可以查看选中需求的内容 + +2010-02-22 0.5 beta发布。 + + 本版本主要完善了需求的处理流程,测试用例的管理和执行以及相应的界面调整。同时完成了bugfree1.x版本的转换工具。 + + #031 需求完善审核流程 + #126 创建需求时,可以指定是否需要走评审 + #128 需求评审时,可以手工指定评审日期 + #131 当评审结果为关闭且原因为已完成或延期时,评审结果应该为pass + #125 延期的需求需要能够重新激活 + #045 当需求有细分任务的时候,自动调整对应需求的所处的阶段 + #023 需求相应的变动可以抄送给相关人员 + #129 需求列表页面增加变更、评审、关闭的操作链接 + #117 需求搜索时,可以按照计划进行搜索 + #118 调整需求详情的显示界面 + #119 调整任务详情的界面 + #113 优化action的显示 + #121 测试用例可以按照步骤来进行填写 + #106 test run管理 + #105 test task管理 + #104 test suite管理 + #124 执行用例的时候,增加全部步骤通过功能 + #026 开发bugfree1.x到pms的转换工具 + +2010-01-26 0.4 beta发布。 + + 1. 项目增加了build功能。 + 2. 产品增加了发布和路线图功能。 + 3. Bug管理增加了对build的支持。 + 4. 新增了升级功能。 + 5. 我的地盘中增加了“我的需求”功能。 + 6. 需求增加了搜索功能,完善了bug和case的搜索功能。 + 7. 需求增加了上传附件功能。 + 8. 调整了bug的显示和编辑界面,使之更加合理清晰。 + 9. 完善了后台分组管理的界面。 + 10. 修复了之前版本的若干bug。 + +2010-01-03 0.3 beta发布 + + 产品管理:提供了产品的增删改,产品的模块管理,需求管理,计划管理 + 项目管理:提供了项目的增删改,关联产品,关联需求,团队管理,任务管理 + 测试管理:提供了完成的Bug跟踪流程(功能同BugFree)和基本的测试用例管理。 + 组织管理:提供了用户的增删改,分组管理和权限管理。 + 我的地盘:提供了todo管理、我的任务、我的bug、我的项目等功能。 + +2009-10-06 0.1 alpha发布。 + + 增加了QA视图,目前主要完成了Bug的基本管理。 + 增加了组织视图,可以用来维护公司的组织结构。 + 增加了我的地盘,可以非常方便的看到参加的项目,所有指派给我的任务及相关的bug。 + 完善了权限管理。 + 部分提升了用户的体验。 + 基于ZenTaoPHP1.2版本架构。 + +2009-09-10 0.02 alpha发布。 +2009-07-30 0.01 alpha发布。 diff --git a/trunk/doc/COPYING b/trunk/doc/COPYING new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/trunk/doc/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/trunk/doc/COPYING.LESSER b/trunk/doc/COPYING.LESSER new file mode 100644 index 0000000000..fc8a5de7ed --- /dev/null +++ b/trunk/doc/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/trunk/doc/INSTALL b/trunk/doc/INSTALL new file mode 100644 index 0000000000..bc5eaae677 --- /dev/null +++ b/trunk/doc/INSTALL @@ -0,0 +1,45 @@ +1. system required + +zentao needs apache, php(>5.2) and mysql. + +2. how to install on windows + + 2.1 use uniserver package on windows. + + uniserver is a very good AMP package. please see http://www.uniformserver.com/ + + We have bind zentao to uniserver, so you can only download our .exe package, unpack it, then you can use zentao now. + + 2.1.1 download our .exe package. + 2.1.2 unpack it. + + after download the package, save it to a path(no space), for example c:\ + then double click the exe file, it will be extracted to c:\zentao + + 2.1.3 start zentao + + cd c:\zentao, there'is a start.exe. double click it, it will stay on the tray menu at the bottom of the window. + + click on the blue one icon, then the menu will show, you can choose the "start apache and mysql", waiting for a moment. + Then you can visit zentao through http://localhost/zentao/ + + 2.1.4 the admin account and password. + + the default admin account is 'admin' and password is '123456'. the root password of mysql is 'root'; + + 2.2 install from the sourcecode on windows, linux or freebsd. + + 2.2.1 install one AMP package. + you can install other AMP package. Make sure the version of apache, php and mysql meets the needs of zetnao. + + 2.2.2 download our zip package. + + 2.2.3 unpack it. + + after download our zip package, save it to the apache's htdocs path and unpack it to zentaopms. make sure the zentaopms path can be + visited through browse. + + 2.2.4 start the installation + + open your browser and visit the zentaopms path, for example http://192.168.1.1/zentaopms/, then the install wizard will launch, you can install it + step by step. diff --git a/trunk/doc/README b/trunk/doc/README new file mode 100644 index 0000000000..6f2feb5142 --- /dev/null +++ b/trunk/doc/README @@ -0,0 +1,29 @@ +zentaopms is a scrum management system which is licensed under LGPLV3. + +The key feature of zentaopms: + +1. product management(product backlog) + + story management + plan management + release management + +2. project management(sprint backlog) + + task management + team management + build management + burn down chart + +3. QA management + + bug tracking + test case management + test task management + +4. document management +5. user management +6. todo feature + +the english version site of zentaopms is http://en.zentao.net +the english demo site of zentaopms is http://endemo.zentao.net. diff --git a/trunk/doc/README.zh_CN b/trunk/doc/README.zh_CN new file mode 100644 index 0000000000..152d4fe9a6 --- /dev/null +++ b/trunk/doc/README.zh_CN @@ -0,0 +1,44 @@ +一、什么是禅道项目管理软件 + +禅道项目管理软件(ZenTaoPMS)是一款国产的,基于LGPL协议,开源免费的项目管理软件,它集产品管理、项目管理、测试管理于一体,同时还包含了事务管理、组织管理等诸多功能,是中小型企业项目管理的首选。官方网站:www.ZenTaoPMS.com + +禅道项目管理软件使用PHP + MySQL开发,基于自主的PHP开发框架──ZenTaoPHP而成。第三方开发者或者企业可以非常方便的开发插件或者进行定制。 + +禅道在手,项目无忧! + +禅道项目管理软件由QingDao Nature Easy Soft Network Technology Co,LTD (http://www.cnezsoft.com)开发。 + +二、禅道的含义 + +Zen是“禅”的意思,Tao是“道”的意思。ZenTao合意为禅与道。这个名称是我在读《编程之禅》和《编程之道》的时候受到启发,决定采用这个比较有中国味道的名字。 + +三、禅道项目管理软件的来由: + +说到这个问题,要从BugFree谈起。自从04年发布BugFree以来到2007年,BugFree陆续发布了五六个版本,在Bug管理方面基本上已经完备。但这个时候产生了一些新的问题。很多网友拿着BugFree进行改动,改造为其他的管理系统。还经常有网友问,BugFree可以不可以加入项目管理的功能。我也一直在思考这些问题。后来我加入了阿里巴巴,在中国雅虎‘、阿里妈妈、淘宝三年的工作经历中,参加了大大小小的项目。也深为项目管理所困惑。于是就产生了做一个工具来解决项目管理的问题。 + +由于种种原因,我这个愿望在阿里巴巴并没有实现。说来也是一件幸事,这样我可以把它开源来发布。从今年初我就开始着手考虑这套管理软件的设计。整整花了半年的时间在考虑。7月份从阿里巴巴辞职之后,我开始有有了相对比较多的一点时间来进行这套系统的开发。同时也非常感谢现在的这家单位,为我开发禅道项目管理软件提供了大力的支持。 + +四、为什么还要做禅道项目管理软件: + +很多朋友会问,已经有很多的开源项目管理软件或者系统了,为什么还要自己做一个呢?主要原因是我认为现在的开源项目管理软件对这个问题解决的并不好,比如缺乏需求管理,bug管理等功能。而且很多开源的项目管理软件使用起来也不方便。 + +市场上也有很多商业的软件,但收费都不菲,而且也未必见得好用。 + +现在也有很多在线的项目管理服务,比如国外的basecamp,国内的易度,忙吧等。但我的观点,这些在线的项目管理软件功能有限,缺乏定制,访问速度无法保证,而且缺乏保密。 + +五、禅道项目管理软件的特点: + +集成了产品管理、项目管理、测试管理、人员管理、发布管理、事务管理等功能于一体。你只需要一个软件就可以完成项目管理的最核心的任务。 +开源免费,降低企业部署的成本。 +功能注重实效,使用方便,没有太多复杂的概念。我设计的理念是一个没有做过项目管理的人经过10分钟的培训可以使用它进行项目管理。:) +基于PHP+MySQL开发,企业自主改动方便。并且基于ZenTaoPHP框架,为第三方开发者的加入打下了坚实的基础。 +主要理念基于scrum,同时结合了PMP里面的很多概念。 +支持多公司,多项目,多产品,多团队的开发。 +灵活的权限设置。 +支持产品与项目之间的矩阵关系。 + +六、禅道项目管理软件需要您的帮助 + +禅道项目管理软件需要您的帮助,无论您是开发人员,抑或是项目经理,或者是产品经理,还是测试人员,又或者是开源爱好者,您都可以在禅道项目管理软件找到参与的地方。我们会以非常open的心态打造一个禅道项目管理软件的社区。我相信,禅道社区的成功,才是禅道项目管理软件的成功。 + +大家如果想加入,可以和我联系:chunsheng@cnezsoft.com diff --git a/trunk/doc/THANKS b/trunk/doc/THANKS new file mode 100644 index 0000000000..37366cf102 --- /dev/null +++ b/trunk/doc/THANKS @@ -0,0 +1,26 @@ + 鸣谢 + +禅道项目管理软件在开发过程中,得到了众多朋友的支持,在此表示感谢。 +下面是不完全名单,如有遗漏,烦请告知,chunsheng@cnezsoft.com + +感谢之前所有参与或帮助过BugFree1.x的朋友们,有你们陪着一路走来,才有了禅道今天的发展,尤其感谢原来的版主 Mr.lee,希望有机会再合作; +感谢BugFree2.x项目的团队,和你们的分歧,最终促使我来做一款真正的项目管理软件,而非仅仅微软的复制品; +感谢原阿里巴巴的同事梅坚先生,是你将scrum介绍到了中国雅虎,正是那时我开始了解学习scrum; +感谢原阿里巴巴的同事史峰先生,感谢你在禅道项目设计初期提出的很多建议; +感谢我的好朋友李玉鹏,感谢我们之间心有灵犀的讨论,确定了禅道很多的设计方案; +感谢原阿里巴巴的其他同事们,禅道的很多想法正是和你们工作时碰撞产生的火花; +感谢普加网(青岛普加智能信息有限公司,www.pujia.com)在禅道前期开发过程中给予的大力支持; +感谢whjmyu, sophia, 21bird, 云飞扬对禅道项目的贡献,谢谢你们的辛勤付出; +感谢sunlei热心的回答网友的问题,再次感谢; +感谢众多网友积极给予的建议和反馈,有你们的参与,禅道会越来越好; +感谢ELING(QQ:467303906)设计了精美的Logo,感谢蓝虎魄(QQ:649009995)设计了禅道1.0正式版的界面; +感谢我的家人对我的理解和支持! + +感谢fujia,ludi,感谢你们大半年来对禅道付出的心血和汗水! +代表禅道所有的用户感谢大頭,感谢创意可米(厦门)网络科技有限公司对禅道的战略投资! www.comemind.com + +恰是面朝大海,春暖花开的日子,禅道2011年的征程已经开始了!加油! + + 王春生 青岛易软天创网络科技有限公司 + 2011-3-13 于青岛开发区 + diff --git a/trunk/doc/THIRDPARTY b/trunk/doc/THIRDPARTY new file mode 100644 index 0000000000..a1d1ffb158 --- /dev/null +++ b/trunk/doc/THIRDPARTY @@ -0,0 +1,8 @@ +禅道项目管理软件使用到的第三方类库列表 + +CSS框架 :YUI 3 beta 中的 CSS套件,包括basic.css, reset.css, font.css和grid.css。官方网站:http://developer.yahoo.com/yui/3/ +JS框架 :JQUERY 1.3版本。官方网站:http://www.jquery.com +JQUER插件:autocomplete colorbox colorize incsearch tablesorter treeview table2csv datepicker alert reverseorder treetable +MAIL类 :phpmailer 5.1,官方网站: http://phpmailer.sourceforge.net +HTTP类 :snoopy 官方网站:http://snoopy.sourceforge.net/ +报表框架 :fushioncharts free 官方网站:http://www.fusioncharts.com/free/ diff --git a/trunk/doc/TODO b/trunk/doc/TODO new file mode 100644 index 0000000000..fd11e0a2fe --- /dev/null +++ b/trunk/doc/TODO @@ -0,0 +1,9 @@ + 多语言(英语,中文繁体,日语,韩语,德语,法语) + 文档管理功能 + 源代码集成 + 讨论管理 + 反馈管理 + 任务功能细化 + 首页细化 + + 更多列表,请访问 http://pms.ZenTaoPMS.com/product.html diff --git a/trunk/doc/UPGRADE b/trunk/doc/UPGRADE new file mode 100644 index 0000000000..d06e01ed2c --- /dev/null +++ b/trunk/doc/UPGRADE @@ -0,0 +1 @@ +升级,请参考 http://www.zentao.net/article-view-78960.html diff --git a/trunk/framework/control.class.php b/trunk/framework/control.class.php new file mode 100755 index 0000000000..99f4427bd7 --- /dev/null +++ b/trunk/framework/control.class.php @@ -0,0 +1,580 @@ +app. + * 2. set the pathes of current module, and load it's mode class. + * 3. auto assign the $lang and $config to the view. + * + * @access public + * @return void + */ + public function __construct($moduleName = '', $methodName = '') + { + /* Global the globals, and refer them to the class member. */ + global $app, $config, $lang, $dbh; + $this->app = $app; + $this->config = $config; + $this->lang = $lang; + $this->dbh = $dbh; + $this->pathFix = $this->app->getPathFix(); + $this->viewType = $this->app->getViewType(); + + $this->setModuleName($moduleName); + $this->setMethodName($methodName); + + /* Load the model file auto. */ + $this->loadModel(); + + /* Assign them to the view. */ + $this->assign('app', $app); + $this->assign('lang', $lang); + $this->assign('config', $config); + + $this->setSuperVars(); + } + + //-------------------- Model related methods --------------------// + + /* Set the module name. + * + * @param string $moduleName The module name, if empty, get it from $app. + * @access private + * @return void + */ + private function setModuleName($moduleName = '') + { + $this->moduleName = $moduleName ? strtolower($moduleName) : $this->app->getModuleName(); + } + + /* Set the method name. + * + * @param string $methodName The method name, if empty, get it from $app. + * @access private + * @return void + */ + private function setMethodName($methodName = '') + { + $this->methodName = $methodName ? strtolower($methodName) : $this->app->getMethodName(); + } + + /** + * Load the model file of one module. + * + * @param string $methodName The method name, if empty, use current module's name. + * @access public + * @return object|bool If no model file, return false. Else return the model object. + */ + public function loadModel($moduleName = '') + { + if(empty($moduleName)) $moduleName = $this->moduleName; + $modelFile = helper::setModelFile($moduleName); + + /* If no model file, try load config. */ + if(!helper::import($modelFile)) + { + $this->app->loadConfig($moduleName, false); + $this->app->loadLang($moduleName); + $this->dao = new dao(); + return false; + } + + $modelClass = class_exists('ext' . $moduleName. 'model') ? 'ext' . $moduleName . 'model' : $moduleName . 'model'; + if(!class_exists($modelClass)) $this->app->error(" The model $modelClass not found", __FILE__, __LINE__, $exit = true); + + $this->$moduleName = new $modelClass(); + $this->dao = $this->$moduleName->dao; + return $this->$moduleName; + } + + /** + * Set the super vars. + * + * @access protected + * @return void + */ + protected function setSuperVars() + { + $this->post = $this->app->post; + $this->get = $this->app->get; + $this->server = $this->app->server; + $this->session = $this->app->session; + $this->cookie = $this->app->cookie; + $this->global = $this->app->global; + } + + //-------------------- View related methods --------------------// + + /** + * Set the view file, thus can use fetch other module's page. + * + * @param string $moduleName module name + * @param string $methodName method name + * @access private + * @return string the view file + */ + private function setViewFile($moduleName, $methodName) + { + $moduleName = strtolower(trim($moduleName)); + $methodName = strtolower(trim($methodName)); + + $modulePath = $this->app->getModulePath($moduleName); + $viewExtPath = $this->app->getModuleExtPath($moduleName, 'view'); + + /* The main view file, extension view file and hook file. */ + $mainViewFile = $modulePath . 'view' . $this->pathFix . $methodName . '.' . $this->viewType . '.php'; + $extViewFile = $viewExtPath . $methodName . ".{$this->viewType}.php"; + $extHookFiles = glob($viewExtPath . $methodName . "*.{$this->viewType}.hook.php"); + + $viewFile = file_exists($extViewFile) ? $extViewFile : $mainViewFile; + if(!is_file($viewFile)) $this->app->error("the view file $viewFile not found", __FILE__, __LINE__, $exit = true); + if(!empty($extHookFiles)) return array('viewFile' => $viewFile, 'hookFiles' => $extHookFiles); + return $viewFile; + } + + /** + * Get the extension file of an view. + * + * @param string $viewFile + * @access public + * @return string|bool If extension view file exists, return the path. Else return fasle. + */ + public function getExtViewFile($viewFile) + { + $extPath = dirname(dirname(realpath($viewFile))) . '/ext/view/'; + $extViewFile = $extPath . basename($viewFile); + if(file_exists($extViewFile)) + { + helper::cd($extPath); + return $extViewFile; + } + return false; + } + + /** + * Get css code for a method. + * + * @param string $moduleName + * @param string $methodName + * @access private + * @return string + */ + private function getCSS($moduleName, $methodName) + { + $moduleName = strtolower(trim($moduleName)); + $methodName = strtolower(trim($methodName)); + $modulePath = $this->app->getModulePath($moduleName); + $cssExtPath = $this->app->getModuleExtPath($moduleName, 'css') . $methodName . $this->pathFix; + + $css = ''; + $mainCssFile = $modulePath . 'css' . $this->pathFix . 'common.css'; + $methodCssFile = $modulePath . 'css' . $this->pathFix . $methodName . '.css'; + if(file_exists($mainCssFile)) $css .= file_get_contents($mainCssFile); + if(is_file($methodCssFile)) $css .= file_get_contents($methodCssFile); + + $cssExtFiles = glob($cssExtPath . '*.css'); + if(is_array($cssExtFiles)) + { + foreach($cssExtFiles as $cssFile) + { + $css .= file_get_contents($cssFile); + } + } + return $css; + } + + /** + * Get js code for a method. + * + * @param string $moduleName + * @param string $methodName + * @access private + * @return string + */ + private function getJS($moduleName, $methodName) + { + $moduleName = strtolower(trim($moduleName)); + $methodName = strtolower(trim($methodName)); + $modulePath = $this->app->getModulePath($moduleName); + $jsExtPath = $this->app->getModuleExtPath($moduleName, 'js') . $methodName . $this->pathFix; + + $js = ''; + $mainJsFile = $modulePath . 'js' . $this->pathFix . 'common.js'; + $methodJsFile = $modulePath . 'js' . $this->pathFix . $methodName . '.js'; + if(file_exists($mainJsFile)) $js .= file_get_contents($mainJsFile); + if(is_file($methodJsFile)) $js .= file_get_contents($methodJsFile); + + $jsExtFiles = glob($jsExtPath . '*.js'); + if(is_array($jsExtFiles)) + { + foreach($jsExtFiles as $jsFile) + { + $js .= file_get_contents($jsFile); + } + } + return $js; + } + + /** + * Assign one var to the view vars. + * + * @param string $name the name. + * @param mixed $value the value. + * @access public + * @return void + */ + public function assign($name, $value) + { + $this->view->$name = $value; + } + + /** + * Clear the output. + * + * @access public + * @return void + */ + public function clear() + { + $this->output = ''; + } + + /** + * Parse view file. + * + * @param string $moduleName module name, if empty, use current module. + * @param string $methodName method name, if empty, use current method. + * @access public + * @return string the parsed result. + */ + public function parse($moduleName = '', $methodName = '') + { + if(empty($moduleName)) $moduleName = $this->moduleName; + if(empty($methodName)) $methodName = $this->methodName; + + if($this->viewType == 'json') + { + $this->parseJSON($moduleName, $methodName); + } + else + { + $this->parseDefault($moduleName, $methodName); + } + return $this->output; + } + + /** + * Parse json format. + * + * @param string $moduleName module name + * @param string $methodName method name + * @access private + * @return void + */ + private function parseJSON($moduleName, $methodName) + { + unset($this->view->app); + unset($this->view->config); + unset($this->view->lang); + unset($this->view->pager); + unset($this->view->header); + unset($this->view->position); + unset($this->view->moduleTree); + + $output['status'] = is_object($this->view) ? 'success' : 'fail'; + $output['data'] = json_encode($this->view); + $output['md5'] = md5(json_encode($this->view)); + $this->output = json_encode($output); + } + + /** + * Parse default html format. + * + * @param string $moduleName module name + * @param string $methodName method name + * @access private + * @return void + */ + private function parseDefault($moduleName, $methodName) + { + /* Set the view file. */ + $viewFile = $this->setViewFile($moduleName, $methodName); + if(is_array($viewFile)) extract($viewFile); + + /* Get css and js. */ + $css = $this->getCSS($moduleName, $methodName); + $js = $this->getJS($moduleName, $methodName); + if($css) $this->view->pageCss = $css; + if($js) $this->view->pageJS = $js; + + /* Change the dir to the view file to keep the relative pathes work. */ + $currentPWD = getcwd(); + chdir(dirname($viewFile)); + + extract((array)$this->view); + ob_start(); + include $viewFile; + if(isset($hookFiles)) foreach($hookFiles as $hookFile) include $hookFile; + $this->output .= ob_get_contents(); + ob_end_clean(); + + /* At the end, chang the dir to the previous. */ + chdir($currentPWD); + } + + /** + * Get the output of one module's one method as a string, thus in one module's method, can fetch other module's content. + * + * If the module name is empty, then use the current module and method. If set, use the user defined module and method. + * + * @param string $moduleName module name. + * @param string $methodName method name. + * @param array $params params. + * @access public + * @return string the parsed html. + */ + public function fetch($moduleName = '', $methodName = '', $params = array()) + { + if($moduleName == '') $moduleName = $this->moduleName; + if($methodName == '') $methodName = $this->methodName; + if($moduleName == $this->moduleName and $methodName == $this->methodName) + { + $this->parse($moduleName, $methodName); + return $this->output; + } + + /* Set the pathes and files to included. */ + $modulePath = $this->app->getModulePath($moduleName); + $moduleControlFile = $modulePath . 'control.php'; + $actionExtFile = $this->app->getModuleExtPath($moduleName, 'control') . strtolower($methodName) . '.php'; + $file2Included = file_exists($actionExtFile) ? $actionExtFile : $moduleControlFile; + + /* Load the control file. */ + if(!is_file($file2Included)) $this->app->error("The control file $file2Included not found", __FILE__, __LINE__, $exit = true); + $currentPWD = getcwd(); + chdir(dirname($file2Included)); + if($moduleName != $this->moduleName) helper::import($file2Included); + + /* Set the name of the class to be called. */ + $className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName; + if(!class_exists($className)) $this->app->error(" The class $className not found", __FILE__, __LINE__, $exit = true); + + /* Parse the params, create the $module control object. */ + if(!is_array($params)) parse_str($params, $params); + $module = new $className($moduleName, $methodName); + + /* Call the method and use ob function to get the output. */ + ob_start(); + call_user_func_array(array($module, $methodName), $params); + $output = ob_get_contents(); + ob_end_clean(); + + /* Return the content. */ + unset($module); + chdir($currentPWD); + return $output; + } + + /** + * Print the content of the view. + * + * @param string $moduleName module name + * @param string $methodName method name + * @access public + * @return void + */ + public function display($moduleName = '', $methodName = '') + { + if(empty($this->output)) $this->parse($moduleName, $methodName); + echo $this->output; + } + + /** + * Create a link to one method of one module. + * + * @param string $moduleName module name + * @param string $methodName method name + * @param string|array $vars the params passed, can be array(key=>value) or key1=value1&key2=value2 + * @param string $viewType the view type + * @access public + * @return string the link string. + */ + public function createLink($moduleName, $methodName = 'index', $vars = array(), $viewType = '') + { + if(empty($moduleName)) $moduleName = $this->moduleName; + return helper::createLink($moduleName, $methodName, $vars, $viewType); + } + + /** + * Create a link to the inner method of current module. + * + * @param string $methodName method name + * @param string|array $vars the params passed, can be array(key=>value) or key1=value1&key2=value2 + * @param string $viewType the view type + * @access public + * @return string the link string. + */ + public function inlink($methodName = 'index', $vars = array(), $viewType = '') + { + return helper::createLink($this->moduleName, $methodName, $vars, $viewType); + } + + /** + * Location to another page. + * + * @param string $url the target url. + * @access public + * @return void + */ + public function locate($url) + { + header("location: $url"); + exit; + } +} diff --git a/trunk/framework/helper.class.php b/trunk/framework/helper.class.php new file mode 100644 index 0000000000..60836d6f76 --- /dev/null +++ b/trunk/framework/helper.class.php @@ -0,0 +1,523 @@ + + * db->user = 'wwccss'; + * helper::setMember('lang', 'db.user', 'chunsheng.wang'); + * ?> + * + * @param string $objName the var name of the object. + * @param string $key the key of the member, can be parent.child. + * @param mixed $value the value to be set. + * @static + * @access public + * @return bool + */ + static public function setMember($objName, $key, $value) + { + global $$objName; + if(!is_object($$objName) or empty($key)) return false; + $key = str_replace('.', '->', $key); + $value = serialize($value); + $code = ("\$${objName}->{$key}=unserialize(<< + * 'value1', 'var2' => 'value2'); + * ?> + * + * @param string $moduleName module name + * @param string $methodName method name + * @param string|array $vars the params passed to the method, can be array('key' => 'value') or key1=value1&key2=value2) or key1=value1&key2=value2 + * @param string $viewType the view type + * @static + * @access public + * @return string the link string. + */ + static public function createLink($moduleName, $methodName = 'index', $vars = '', $viewType = '') + { + global $app, $config; + $link = $config->requestType == 'PATH_INFO' ? $config->webRoot : $_SERVER['PHP_SELF']; + + /* Set the view type and vars. */ + if(empty($viewType)) $viewType = $app->getViewType(); + if(!is_array($vars)) parse_str($vars, $vars); + + /* The PATH_INFO type. */ + if($config->requestType == 'PATH_INFO') + { + /* If the method equal the default method defined in the config file and the vars is empty, convert the link. */ + if($methodName == $config->default->method and empty($vars)) + { + /* If the module also equal the default module, change index-index to index.html. */ + if($moduleName == $config->default->module) + { + $link .= 'index.' . $viewType; + } + else + { + $link .= $moduleName . '/'; + } + } + else + { + $link .= "$moduleName{$config->requestFix}$methodName"; + if($config->pathType == 'full') + { + foreach($vars as $key => $value) $link .= "{$config->requestFix}$key{$config->requestFix}$value"; + } + else + { + foreach($vars as $value) $link .= "{$config->requestFix}$value"; + } + $link .= '.' . $viewType; + } + } + elseif($config->requestType == 'GET') + { + $link .= "?{$config->moduleVar}=$moduleName&{$config->methodVar}=$methodName"; + if($viewType != 'html') $link .= "&{$config->viewVar}=" . $viewType; + foreach($vars as $key => $value) $link .= "&$key=$value"; + } + + /* if page has onlybody param then add this param in all link. the param hide header and footer. */ + if(isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes') + { + $onlybody = $config->requestType == 'PATH_INFO' ? "?onlybody=yes" : "&onlybody=yes"; + $link .= $onlybody; + } + return $link; + } + + /** + * Import a file instend of include or requie. + * + * @param string $file the file to be imported. + * @static + * @access public + * @return bool + */ + static public function import($file) + { + static $includedFiles = array(); + if(!isset($includedFiles[$file])) + { + $return = include $file; + if(!$return) return false; + $includedFiles[$file] = true; + return true; + } + return true; + } + + /** + * Set the model file of one module. If there's an extension file, merge it with the main model file. + * + * @param string $moduleName the module name + * @static + * @access public + * @return string the model file + */ + static public function setModelFile($moduleName) + { + global $app; + + /* Set the main model file and extension and hook pathes and files. */ + $mainModelFile = $app->getModulePath($moduleName) . 'model.php'; + $modelExtPath = $app->getModuleExtPath($moduleName, 'model'); + $modelHookPath = $modelExtPath . 'hook/'; + $extFiles = helper::ls($modelExtPath, '.php'); + $hookFiles = helper::ls($modelHookPath, '.php'); + + /* If no extension files and no hook files, return the main file directly. */ + if(empty($extFiles) and empty($hookFiles)) return $mainModelFile; + + /* Else, judge whether needed update or not .*/ + $needUpdate = false; + $mergedModelFile = $app->getTmpRoot() . 'model' . $app->getPathFix() . $moduleName . '.php'; + $lastTime = file_exists($mergedModelFile) ? filemtime($mergedModelFile) : 0; + + while(!$needUpdate) + { + foreach($extFiles as $extFile) if(filemtime($extFile) > $lastTime) break 2; + foreach($hookFiles as $hookFile) if(filemtime($hookFile) > $lastTime) break 2; + + if(is_dir($modelExtPath ) and filemtime($modelExtPath) > $lastTime) break; + if(is_dir($modelHookPath) and filemtime($modelHookPath) > $lastTime) break; + + if(filemtime($mainModelFile) > $lastTime) break; + + return $mergedModelFile; + } + + /* Update the cache file. */ + $modelClass = $moduleName . 'Model'; + $extModelClass = 'ext' . $modelClass; + $extTmpModelClass = 'tmpExt' . $modelClass; + $modelLines = "getFileName(); + $startLine = $methodRelfection->getStartLine() . ' '; + $endLine = $methodRelfection->getEndLine() . ' '; + + /* Merge hook codes. */ + $oldCodes = $definedFile == $mergedModelFile ? $mergedModelCodes : $mainModelCodes; + $oldCodes = join("", array_slice($oldCodes, $startLine - 1, $endLine - $startLine + 1)); + $openBrace = strpos($oldCodes, '{'); + $newCodes = substr($oldCodes, 0, $openBrace + 1) . "\n" . join("\n", $hookCodes[$method]) . substr($oldCodes, $openBrace + 1); + + /* Replace it. */ + if($definedFile == $mergedModelFile) + { + $modelLines = str_replace($oldCodes, $newCodes, $modelLines); + } + else + { + $modelLines = str_replace($replaceMark, $newCodes . "\n$replaceMark", $modelLines); + } + } + + /* Save it. */ + $modelLines = str_replace($extTmpModelClass, $extModelClass, $modelLines); + file_put_contents($mergedModelFile, $modelLines); + + return $mergedModelFile; + } + + /** + * Remove tags of PHP + * + * @param string $fileName + * @static + * @access public + * @return string + */ + static public function removeTagsOfPHP($fileName) + { + $code = trim(file_get_contents($fileName)); + if(strpos($code, '') !== false) $code = rtrim($code, '?>'); + return trim($code); + } + + /** + * Create the in('a', 'b') string. + * + * @param string|array $ids the id lists, can be a array or a string with ids joined with comma. + * @static + * @access public + * @return string the string like IN('a', 'b'). + */ + static public function dbIN($ids) + { + if(is_array($ids)) return "IN ('" . join("','", $ids) . "')"; + return "IN ('" . str_replace(',', "','", str_replace(' ', '',$ids)) . "')"; + } + + /** + * Create safe base64 encoded string for the framework. + * + * @param string $string the string to encode. + * @static + * @access public + * @return string encoded string. + */ + static public function safe64Encode($string) + { + return strtr(base64_encode($string), '/', '.'); + } + + /** + * Decode the string encoded by safe64Encode. + * + * @param string $string the string to decode + * @static + * @access public + * @return string decoded string. + */ + static public function safe64Decode($string) + { + return base64_decode(strtr($string, '.', '/')); + } + + /** + * Judge a string is utf-8 or not. + * + * @param string $string + * @author hmdker@gmail.com + * @see http://php.net/manual/en/function.mb-detect-encoding.php + * @static + * @access public + * @return bool + */ + static public function isUTF8($string) + { + $c = 0; + $b = 0; + $bits = 0; + $len = strlen($string); + for($i=0; $i<$len; $i++) + { + $c = ord($str[$i]); + if($c > 128) + { + if(($c >= 254)) return false; + elseif($c >= 252) $bits=6; + elseif($c >= 248) $bits=5; + elseif($c >= 240) $bits=4; + elseif($c >= 224) $bits=3; + elseif($c >= 192) $bits=2; + else return false; + if(($i+$bits) > $len) return false; + while($bits > 1) + { + $i++; + $b=ord($str[$i]); + if($b < 128 || $b > 191) return false; + $bits--; + } + } + } + return true; + } + + /** + * Compute the diff days of two date. + * + * @param date $date1 the first date. + * @param date $date2 the sencode date. + * @access public + * @return int the diff of the two days. + */ + static public function diffDate($date1, $date2) + { + return round((strtotime($date1) - strtotime($date2)) / 86400, 0); + } + + /** + * Get now time use the DT_DATETIME1 constant defined in the lang file. + * + * @access public + * @return datetime now + */ + static public function now() + { + return date(DT_DATETIME1); + } + + /** + * Get today according to the DT_DATE1 constant defined in the lang file. + * + * @access public + * @return date today + */ + static public function today() + { + return date(DT_DATE1); + } + + /** + * Judge a date is zero or not. + * + * @access public + * @return bool + */ + static public function isZeroDate($date) + { + return substr($date, 0, 4) == '0000'; + } + + /** + * Get files match the pattern under one directory. + * + * @access public + * @return array the files match the pattern + */ + static public function ls($dir, $pattern = '') + { + $files = array(); + $dir = realpath($dir); + if(is_dir($dir)) + { + if($dh = opendir($dir)) + { + while(($file = readdir($dh)) !== false) + { + if(strpos($file, $pattern) !== false) $files[] = $dir . DIRECTORY_SEPARATOR . $file; + } + closedir($dh); + } + } + return $files; + } + + /** + * Change directory. + * + * @param string $path + * @static + * @access public + * @return void + */ + static function cd($path = '') + { + static $cwd = ''; + if($path) + { + $cwd = getcwd(); + chdir($path); + } + else + { + chdir($cwd); + } + } +} + +/** + * The short alias of helper::createLink() method. + * + * @param string $methodName the method name + * @param string|array $vars the params passed to the method, can be array('key' => 'value') or key1=value1&key2=value2) + * @param string $viewType + * @return string the link string. + */ +function inLink($methodName = 'index', $vars = '', $viewType = '') +{ + global $app; + return helper::createLink($app->getModuleName(), $methodName, $vars, $viewType); +} + +/** + * Static cycle a array + * + * @param array $items the array to be cycled. + * @return mixed + */ +function cycle($items) +{ + static $i = 0; + if(!is_array($items)) $items = explode(',', $items); + if(!isset($items[$i])) $i = 0; + return $items[$i++]; +} + +/** + * Get current microtime. + * + * @access protected + * @return float current time. + */ +function getTime() +{ + list($usec, $sec) = explode(" ", microtime()); + return ((float)$usec + (float)$sec); +} + +/** + * Save the sql. + * + * @access protected + * @return void + */ +function saveSQL() +{ + if(!class_exists('dao')) return; + global $app; + $sqlLog = $app->getLogRoot() . 'sql.' . date('Ymd') . '.log'; + $fh = @fopen($sqlLog, 'a'); + if(!$fh) return false; + fwrite($fh, date('Ymd H:i:s') . ": " . $app->getURI() . "\n"); + foreach(dao::$querys as $query) fwrite($fh, " $query\n"); + fwrite($fh, "\n"); + fclose($fh); +} + +/** + * dump a var. + * + * @param mixed $var + * @access public + * @return void + */ +function a($var) +{ + echo ""; + print_r($var); + echo ""; +} + +/** + * Judge the server ip is local or not. + * + * @access public + * @return void + */ +function isLocalIP() +{ + $serverIP = $_SERVER['SERVER_ADDR']; + if($serverIP == '127.0.0.1') return true; + return !filter_var($serverIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); +} diff --git a/trunk/framework/model.class.php b/trunk/framework/model.class.php new file mode 100755 index 0000000000..a88576d400 --- /dev/null +++ b/trunk/framework/model.class.php @@ -0,0 +1,264 @@ +app. + * 2. set the pathes, config, lang of current module + * + * @access public + * @return void + */ + public function __construct() + { + global $app, $config, $lang, $dbh; + $this->app = $app; + $this->config = $config; + $this->lang = $lang; + $this->dbh = $dbh; + + $moduleName = $this->getModuleName(); + $this->app->loadLang($moduleName, $exit = false); + $this->app->loadConfig($moduleName, $exit = false); + + $this->loadDAO(); + $this->setSuperVars(); + } + + /** + * Get the module name of this model. Not the module user visiting. + * + * This method replace the 'ext' and 'model' string from the model class name, thus get the module name. + * Not useing $app->getModuleName() because it return the module user is visiting. But one module can be + * loaded by loadModel() so we must get the module name of thie model. + * + * @access protected + * @return string the module name. + */ + protected function getModuleName() + { + $parentClass = get_parent_class($this); + $selfClass = get_class($this); + $className = $parentClass == 'model' ? $selfClass : $parentClass; + if($className == 'extensionModel') return 'extension'; + return strtolower(str_ireplace(array('ext', 'Model'), '', $className)); + } + + /** + * Set the super vars. + * + * @access protected + * @return void + */ + protected function setSuperVars() + { + $this->post = $this->app->post; + $this->get = $this->app->get; + $this->server = $this->app->server; + $this->cookie = $this->app->cookie; + $this->session = $this->app->session; + $this->global = $this->app->global; + } + + /** + * Load the model of one module. After loaded, can use $this->modulename to visit the model object. + * + * @param string $moduleName + * @access public + * @return object|bool the model object or false if model file not exists. + */ + public function loadModel($moduleName) + { + if(empty($moduleName)) return false; + $modelFile = helper::setModelFile($moduleName); + + if(!helper::import($modelFile)) return false; + $modelClass = class_exists('ext' . $moduleName. 'model') ? 'ext' . $moduleName . 'model' : $moduleName . 'model'; + if(!class_exists($modelClass)) $this->app->error(" The model $modelClass not found", __FILE__, __LINE__, $exit = true); + + $this->$moduleName = new $modelClass(); + return $this->$moduleName; + } + + /** + * Load extension class of a model. Saved to $moduleName/ext/model/class/$extensionName.class.php. + * + * @param string $extensionName + * @param string $moduleName + * @access public + * @return void + */ + public function loadExtension($extensionName, $moduleName = '') + { + if(empty($extensionName)) return false; + + /* Set extenson name and extension file. */ + $extensionName = strtolower($extensionName); + $moduleName = $moduleName ? $moduleName : $this->getModuleName(); + $extensionFile = $this->app->getModuleExtPath($moduleName, 'model') . 'class/' . $extensionName . '.class.php'; + + /* Try to import parent model file auto and then import the extension file. */ + if(!class_exists($moduleName . 'Model')) helper::import($this->app->getModulePath($moduleName) . 'model.php'); + if(!helper::import($extensionFile)) return false; + + /* Set the extension class name. */ + $extensionClass = $extensionName . ucfirst($moduleName); + if(!class_exists($extensionClass)) return false; + + /* Create an instance of the extension class and return it. */ + $extensionObject = new $extensionClass; + $extensionClass = str_replace('Model', '', $extensionClass); + $this->$extensionClass = $extensionObject; + return $extensionObject; + } + + //-------------------- DAO related method s--------------------// + + /** + * Load DAO. + * + * @access private + * @return void + */ + private function loadDAO() + { + $this->dao = $this->app->loadClass('dao'); + } + + /** + * Delete one record. + * + * @param string $table the table name + * @param string $id the id value of the record to be deleted + * @access public + * @return void + */ + public function delete($table, $id) + { + $this->dao->update($table)->set('deleted')->eq(1)->where('id')->eq($id)->exec(); + $object = str_replace($this->config->db->prefix, '', $table); + $this->loadModel('action')->create($object, $id, 'deleted', '', $extra = ACTIONMODEL::CAN_UNDELETED); + } + + /** + * Undelete an record. + * + * @param int $actionID + * @access public + * @return void + */ + public function undelete($actionID) + { + $action = $this->loadModel('action')->getById($actionID); + if($action->action != 'deleted') return; + $table = $this->config->action->objectTables[$action->objectType]; + $this->dao->update($table)->set('deleted')->eq(0)->where('id')->eq($action->objectID)->exec(); + $this->dao->update(TABLE_ACTION)->set('extra')->eq(ACTIONMODEL::BE_UNDELETED)->where('id')->eq($actionID)->exec(); + $this->action->create($action->objectType, $action->objectID, 'undeleted'); + } +} diff --git a/trunk/framework/router.class.php b/trunk/framework/router.class.php new file mode 100755 index 0000000000..a37f7e5736 --- /dev/null +++ b/trunk/framework/router.class.php @@ -0,0 +1,1728 @@ +basePath/framework) + * + * @var string + * @access private + */ + private $frameRoot; + + /** + * The root directory of the core library($this->basePath/lib) + * + * @var string + * @access private + */ + private $coreLibRoot; + + /** + * The root directory of the app. + * + * @var string + * @access private + */ + private $appRoot; + + /** + * The root directory of the app library($this->appRoot/lib). + * + * @var string + * @access private + */ + private $appLibRoot; + + /** + * The root directory of temp. + * + * @var string + * @access private + */ + private $tmpRoot; + + /** + * The root directory of cache. + * + * @var string + * @access private + */ + private $cacheRoot; + + /** + * The root directory of log. + * + * @var string + * @access private + */ + private $logRoot; + + /** + * The root directory of config. + * + * @var string + * @access private + */ + private $configRoot; + + /** + * The root directory of module. + * + * @var string + * @access private + */ + private $moduleRoot; + + /** + * The root directory of theme. + * + * @var string + * @access private + */ + private $themeRoot; + + /** + * The lang of the client user. + * + * @var string + * @access private + */ + private $clientLang; + + /** + * The theme of the client user. + * + * @var string + * @access private + */ + private $clientTheme; + + /** + * The control object of current module. + * + * @var object + * @access public + */ + public $control; + + /** + * The module name + * + * @var string + * @access private + */ + private $moduleName; + + /** + * The control file of the module current visiting. + * + * @var string + * @access private + */ + private $controlFile; + + /** + * The name of the method current visiting. + * + * @var string + * @access private + */ + private $methodName; + + /** + * The action extension file of current method. + * + * @var string + * @access private + */ + private $extActionFile; + + /** + * The URI. + * + * @var string + * @access private + */ + private $URI; + + /** + * The params passed in through url. + * + * @var array + * @access private + */ + private $params; + + /** + * The view type. + * + * @var string + * @access private + */ + private $viewType; + + /** + * The global $config object. + * + * @var object + * @access public + */ + public $config; + + /** + * The global $lang object. + * + * @var object + * @access public + */ + public $lang; + + /** + * The global $dbh object, the database connection handler. + * + * @var object + * @access private + */ + public $dbh; + + /** + * The slave database handler. + * + * @var object + * @access private + */ + public $slaveDBH; + + /** + * The $post object, used to access the $_POST var. + * + * @var ojbect + * @access public + */ + public $post; + + /** + * The $get object, used to access the $_GET var. + * + * @var ojbect + * @access public + */ + public $get; + + /** + * The $session object, used to access the $_SESSION var. + * + * @var ojbect + * @access public + */ + public $session; + + /** + * The $server object, used to access the $_SERVER var. + * + * @var ojbect + * @access public + */ + public $server; + + /** + * The $cookie object, used to access the $_COOKIE var. + * + * @var ojbect + * @access public + */ + public $cookie; + + /** + * The $global object, used to access the $_GLOBAL var. + * + * @var ojbect + * @access public + */ + public $global; + + /** + * The construct function. + * + * Prepare all the paths, classes, super objects and so on. + * Notice: + * 1. You should use the createApp() method to get an instance of the router. + * 2. If the $appRoot is empty, the framework will comput the appRoot according the $appName + * + * @param string $appName the name of the app + * @param string $appRoot the root path of the app + * @access protected + * @return void + */ + protected function __construct($appName = 'demo', $appRoot = '') + { + $this->setPathFix(); + $this->setBasePath(); + $this->setFrameRoot(); + $this->setCoreLibRoot(); + $this->setAppRoot($appName, $appRoot); + $this->setAppLibRoot(); + $this->setTmpRoot(); + $this->setCacheRoot(); + $this->setLogRoot(); + $this->setConfigRoot(); + $this->setModuleRoot(); + $this->setThemeRoot(); + + $this->setSuperVars(); + + $this->loadConfig('common'); + $this->setDebug(); + + $this->connectDB(); + + $this->setTimezone(); + $this->setClientLang(); + $this->loadLang('common'); + $this->setClientTheme(); + + $this->loadClass('front', $static = true); + $this->loadClass('filter', $static = true); + $this->loadClass('dao', $static = true); + } + + /** + * Create an application. + * + * + * + * or specify the root path of the app. Thus the app and framework can be seperated. + * + * + * @param string $appName the name of the app + * @param string $appRoot the root path of the app + * @param string $className the name of the router class. When extends a child, you should pass in the child router class name. + * @static + * @access public + * @return object the app object + */ + public static function createApp($appName = 'demo', $appRoot = '', $className = 'router') + { + if(empty($className)) $className = __CLASS__; + return new $className($appName, $appRoot); + } + + //-------------------- path related methods --------------------// + + /** + * Set the path directory. + * + * @access protected + * @return void + */ + protected function setPathFix() + { + $this->pathFix = DIRECTORY_SEPARATOR; + } + + /** + * Set the base path. + * + * @access protected + * @return void + */ + protected function setBasePath() + { + $this->basePath = realpath(dirname(dirname(__FILE__))) . $this->pathFix; + } + + /** + * Set the frame root. + * + * @access protected + * @return void + */ + protected function setFrameRoot() + { + $this->frameRoot = $this->basePath . 'framework' . $this->pathFix; + } + + /** + * Set the core library root. + * + * @access protected + * @return void + */ + protected function setCoreLibRoot() + { + $this->coreLibRoot = $this->basePath . 'lib' . $this->pathFix; + } + + /** + * Set the app root. + * + * @param string $appName + * @param string $appRoot + * @access protected + * @return void + */ + protected function setAppRoot($appName = 'demo', $appRoot = '') + { + if(empty($appRoot)) + { + $this->appRoot = $this->basePath . 'app' . $this->pathFix . $appName . $this->pathFix; + } + else + { + $this->appRoot = realpath($appRoot) . $this->pathFix; + } + if(!is_dir($this->appRoot)) $this->error("The app you call not noud in {$this->appRoot}", __FILE__, __LINE__, $exit = true); + } + + /** + * Set the app lib root. + * + * @access protected + * @return void + */ + protected function setAppLibRoot() + { + $this->appLibRoot = $this->appRoot . 'lib' . $this->pathFix; + } + + /** + * Set the tmp root. + * + * @access protected + * @return void + */ + protected function setTmpRoot() + { + $this->tmpRoot = $this->appRoot . 'tmp' . $this->pathFix; + } + + /** + * Set the cache root. + * + * @access protected + * @return void + */ + protected function setCacheRoot() + { + $this->cacheRoot = $this->tmpRoot . 'cache' . $this->pathFix; + } + + /** + * Set the log root. + * + * @access protected + * @return void + */ + protected function setLogRoot() + { + $this->logRoot = $this->tmpRoot . 'log' . $this->pathFix; + } + + /** + * Set the config root. + * + * @access protected + * @return void + */ + protected function setConfigRoot() + { + $this->configRoot = $this->appRoot . 'config' . $this->pathFix; + } + + /** + * Set the module root. + * + * @access protected + * @return void + */ + protected function setModuleRoot() + { + $this->moduleRoot = $this->appRoot . 'module' . $this->pathFix; + } + + /** + * Set the theme root. + * + * @access protected + * @return void + */ + protected function setThemeRoot() + { + $this->themeRoot = $this->appRoot . 'www' . $this->pathFix . 'theme' . $this->pathFix; + } + + /** + * Set the super vars. + * + * @access protected + * @return void + */ + public function setSuperVars() + { + $this->post = new super('post'); + $this->get = new super('get'); + $this->server = new super('server'); + $this->cookie = new super('cookie'); + $this->session = new super('session'); + $this->global = new super('global'); + } + + /** + * set Debug + * + * @access public + * @return void + */ + public function setDebug() + { + if(isset($this->config->debug) and $this->config->debug) + { + error_reporting(E_ALL & ~ E_STRICT); + register_shutdown_function('saveSQL'); + } + } + + /** + * Set the time zone according to the config. + * + * @access public + * @return void + */ + public function setTimezone() + { + if(isset($this->config->timezone)) date_default_timezone_set($this->config->timezone); + } + + /** + * Get the $pathFix var + * + * @access public + * @return string + */ + public function getPathFix() + { + return $this->pathFix; + } + + /** + * Get the $basePath var + * + * @access public + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * Get the $frameRoot var + * + * @access public + * @return string + */ + public function getFrameRoot() + { + return $this->frameRoot; + } + + /** + * Get the $coreLibRoot var + * + * @access public + * @return string + */ + public function getCoreLibRoot() + { + return $this->coreLibRoot; + } + + /** + * Get the $appRoot var + * + * @access public + * @return string + */ + public function getAppRoot() + { + return $this->appRoot; + } + + /** + * Get the $appLibRoot var + * + * @access public + * @return string + */ + public function getAppLibRoot() + { + return $this->appLibRoot; + } + + /** + * Get the $tmpRoot var + * + * @access public + * @return string + */ + public function getTmpRoot() + { + return $this->tmpRoot; + } + + /** + * Get the $cacheRoot var + * + * @access public + * @return string + */ + public function getCacheRoot() + { + return $this->cacheRoot; + } + + /** + * Get the $logRoot var + * + * @access public + * @return string + */ + public function getLogRoot() + { + return $this->logRoot; + } + + /** + * Get the $configRoot var + * + * @access public + * @return string + */ + public function getConfigRoot() + { + return $this->configRoot; + } + + /** + * Get the $moduleRoot var + * + * @access public + * @return string + */ + public function getModuleRoot() + { + return $this->moduleRoot; + } + + /** + * Get the $themeRoot var + * + * @access public + * @return string + */ + public function getThemeRoot() + { + return $this->themeRoot; + } + + //-------------------- Client environment related functions --------------------// + + /** + * Set the language used by the client user. + * + * Using the order of method $lang param, session, cookie, browser and last the default lang. + * + * @param string $lang zh-cn|zh-tw|zh-hk|en + * @access public + * @return void + */ + public function setClientLang($lang = '') + { + if(!empty($lang)) + { + $this->clientLang = $lang; + } + elseif(isset($_SESSION['lang'])) + { + $this->clientLang = $_SESSION['lang']; + } + elseif(isset($_COOKIE['lang'])) + { + $this->clientLang = $_COOKIE['lang']; + } + elseif(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + { + if(strpos($_SERVER['HTTP_ACCEPT_LANGUAGE'], ',') === false) + { + $this->clientLang = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + } + else + { + $this->clientLang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, strpos($_SERVER['HTTP_ACCEPT_LANGUAGE'], ',')); + } + } + if(!empty($this->clientLang)) + { + $this->clientLang = strtolower($this->clientLang); + if(!isset($this->config->langs[$this->clientLang])) $this->clientLang = $this->config->default->lang; + } + else + { + $this->clientLang = $this->config->default->lang; + } + setcookie('lang', $this->clientLang, $this->config->cookieLife, $this->config->webRoot); + if(!isset($_COOKIE['lang'])) $_COOKIE['lang'] = $this->clientLang; + } + + /** + * Get the $clientLang var. + * + * @access public + * @return string + */ + public function getClientLang() + { + return $this->clientLang; + } + + /** + * Set the them the client user usering. The logic is same as the clientLang. + * + * The css and images files of an theme should saved at www/theme/$themeName + * + * @param string $theme + * @access public + * @return void + */ + public function setClientTheme($theme = '') + { + if(!empty($theme)) + { + $this->clientTheme = $theme; + } + elseif(isset($_COOKIE['theme'])) + { + $this->clientTheme = $_COOKIE['theme']; + } + elseif(isset($this->config->client->theme)) + { + $this->clientTheme = $this->config->client->theme; + } + + if(!empty($this->clientTheme)) + { + $this->clientTheme = strtolower($this->clientTheme); + } + else + { + $this->clientTheme = $this->config->default->theme; + } + setcookie('theme', $this->clientTheme, $this->config->cookieLife, $this->config->webRoot); + if(!isset($_COOKIE['theme'])) $_COOKIE['theme'] = $this->clientTheme; + } + + /** + * Get the $clientTheme var. + * + * @access public + * @return string + */ + public function getClientTheme() + { + return $this->config->webRoot . 'theme/' . $this->clientTheme . '/'; + } + + /** + * Get the $webRoot var. + * + * @access public + * @return string + */ + public function getWebRoot() + { + return $this->config->webRoot; + } + + //-------------------- Request related methods. --------------------// + + /** + * The entrance of parseing request. According to the requestType, call related methods. + * + * @access public + * @return void + */ + public function parseRequest() + { + if($this->config->requestType == 'PATH_INFO') + { + $this->parsePathInfo(); + $this->setRouteByPathInfo(); + } + elseif($this->config->requestType == 'GET') + { + $this->parseGET(); + $this->setRouteByGET(); + } + else + { + $this->error("The request type {$this->config->requestType} not supported", __FILE__, __LINE__, $exit = true); + } + } + + /** + * Parse PATH_INFO, get the $URI and $viewType. + * + * @access public + * @return void + */ + public function parsePathInfo() + { + $pathInfo = $this->getPathInfo('PATH_INFO'); + if(empty($pathInfo)) $pathInfo = $this->getPathInfo('ORIG_PATH_INFO'); + if(!empty($pathInfo)) + { + $dotPos = strrpos($pathInfo, '.'); + if($dotPos) + { + $this->URI = substr($pathInfo, 0, $dotPos); + $this->viewType = substr($pathInfo, $dotPos + 1); + if(strpos($this->config->views, ',' . $this->viewType . ',') === false) + { + $this->viewType = $this->config->default->view; + } + } + else + { + $this->URI = $pathInfo; + $this->viewType = $this->config->default->view; + } + } + else + { + $this->viewType = $this->config->default->view; + } + } + + /** + * Get $PATH_INFO from $_SERVER or $_ENV by the pathinfo var name. + * + * Mostly, the var name of PATH_INFO is PATH_INFO, but may be ORIG_PATH_INFO. + * + * @param string $varName PATH_INFO, ORIG_PATH_INFO + * @access private + * @return string the PATH_INFO + */ + private function getPathInfo($varName) + { + $value = @getenv($varName); + if(isset($_SERVER[$varName])) $value = $_SERVER[$varName]; + return trim($value, '/'); + } + + /** + * Parse GET, get $URI and $viewType. + * + * @access private + * @return void + */ + private function parseGET() + { + if(isset($_GET[$this->config->viewVar])) + { + $this->viewType = $_GET[$this->config->viewVar]; + if(strpos($this->config->views, ',' . $this->viewType . ',') === false) + { + $this->viewType = $this->config->default->view; + } + } + else + { + $this->viewType = $this->config->default->view; + } + $this->URI = $_SERVER['REQUEST_URI']; + } + + /** + * Get the $URL + * + * @param bool $full true, the URI contains the webRoot, else only hte URI. + * @access public + * @return string + */ + public function getURI($full = false) + { + if($full and $this->config->requestType == 'PATH_INFO') + { + if($this->URI) return $this->config->webRoot . $this->URI . '.' . $this->viewType; + return $this->config->webRoot; + } + return $this->URI; + } + + /** + * Get the $viewType var. + * + * @access public + * @return string + */ + public function getViewType() + { + return $this->viewType; + } + + //-------------------- Routing related methods.--------------------// + + /** + * Load the common module + * + * The common module is a special module, which can be used to do some common things. For examle: + * start session, check priviledge and so on. + * This method should called manually in the router file(www/index.php) after the $lang, $config, $dbh loadde. + * + * @access public + * @return object|bool the common control object or false if not exits. + */ + public function loadCommon() + { + $this->setModuleName('common'); + if($this->setControlFile($exitIfNone = false)) + { + include $this->controlFile; + if(class_exists('common')) + { + return new common(); + } + else + { + return false; + } + } + } + + /** + * Set the name of the module to be called. + * + * @param string $moduleName the module name + * @access public + * @return void + */ + public function setModuleName($moduleName = '') + { + $this->moduleName = strtolower($moduleName); + } + + /** + * Set the control file of the module to be called. + * + * @param bool $exitIfNone If control file not foundde, how to do. True, die the whole app. false, log error. + * @access public + * @return bool + */ + public function setControlFile($exitIfNone = true) + { + $this->controlFile = $this->moduleRoot . $this->moduleName . $this->pathFix . 'control.php'; + if(!is_file($this->controlFile)) + { + $this->error("the control file $this->controlFile not found.", __FILE__, __LINE__, $exitIfNone); + return false; + } + return true; + } + + /** + * Set the name of the method calling. + * + * @param string $methodName + * @access public + * @return void + */ + public function setMethodName($methodName = '') + { + $this->methodName = strtolower($methodName); + } + + /** + * Get the path of one module. + * + * @param string $moduleName the module name + * @access public + * @return string the module path + */ + public function getModulePath($moduleName = '') + { + if($moduleName == '') $moduleName = $this->moduleName; + return $this->getModuleRoot() . strtolower(trim($moduleName)) . $this->pathFix; + } + + /** + * Get extension path of one module. + * + * @param string $moduleName the module name + * @param string $ext the extension type, can be control|model|view|lang|config + * @access public + * @return string the extension path. + */ + public function getModuleExtPath($moduleName, $ext) + { + return $this->getModuleRoot() . strtolower(trim($moduleName)) . $this->pathFix . 'ext' . $this->pathFix . $ext . $this->pathFix; + } + + /** + * Set the action extension file. + * + * @access public + * @return bool + */ + public function setActionExtFile() + { + $moduleExtPath = $this->getModuleExtPath($this->moduleName, 'control'); + $this->extActionFile = $moduleExtPath . $this->methodName . '.php'; + return file_exists($this->extActionFile); + } + + /** + * Set the route according to PATH_INFO. + * + * 1. set the module name. + * 2. set the method name. + * 3. set the control file. + * + * @access public + * @return void + */ + public function setRouteByPathInfo() + { + if(!empty($this->URI)) + { + /* There's the request seperator, split the URI by it. */ + if(strpos($this->URI, $this->config->requestFix) !== false) + { + $items = explode($this->config->requestFix, $this->URI); + $this->setModuleName($items[0]); + $this->setMethodName($items[1]); + } + /* No reqeust seperator, use the default method name. */ + else + { + $this->setModuleName($this->URI); + $this->setMethodName($this->config->default->method); + } + } + else + { + $this->setModuleName($this->config->default->module); // use the default module. + $this->setMethodName($this->config->default->method); // use the default method. + } + $this->setControlFile(); + } + + /** + * Set the route according to GET. + * + * 1. set the module name. + * 2. set the method name. + * 3. set the control file. + * + * @access public + * @return void + */ + public function setRouteByGET() + { + $moduleName = isset($_GET[$this->config->moduleVar]) ? strtolower($_GET[$this->config->moduleVar]) : $this->config->default->module; + $methodName = isset($_GET[$this->config->methodVar]) ? strtolower($_GET[$this->config->methodVar]) : $this->config->default->method; + $this->setModuleName($moduleName); + $this->setControlFile(); + $this->setMethodName($methodName); + } + + /** + * Load a module. + * + * 1. include the control file or the extension action file. + * 2. create the control object. + * 3. set the params passed in through url. + * 4. call the method by call_user_function_array + * + * @access public + * @return bool|object if the module object of die. + */ + public function loadModule() + { + $moduleName = $this->moduleName; + $methodName = $this->methodName; + + /* Include the contror file of the module. */ + $file2Included = $this->setActionExtFile() ? $this->extActionFile : $this->controlFile; + chdir(dirname($file2Included)); + include $file2Included; + + /* Set the class name of the control. */ + $className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName; + if(!class_exists($className)) $this->error("the control $className not found", __FILE__, __LINE__, $exit = true); + + /* Create a instance of the control. */ + $module = new $className(); + if(!method_exists($module, $methodName)) $this->error("the module $moduleName has no $methodName method", __FILE__, __LINE__, $exit = true); + $this->control = $module; + + /* include default value for module*/ + $defaultValueFiles = glob($this->getTmpRoot() . "defaultvalue/*.php"); + if($defaultValueFiles) foreach($defaultValueFiles as $file) include $file; + + /* Get the default setings of the method to be called useing the reflecting. */ + $defaultParams = array(); + $methodReflect = new reflectionMethod($className, $methodName); + + foreach($methodReflect->getParameters() as $param) + { + $name = $param->getName(); + + $default = '_NOT_SET'; + if($param->isDefaultValueAvailable()) + { + $default = $param->getDefaultValue(); + } + elseif(isset($paramDefaultValue[$className][$methodName][$name])) + { + $default = $paramDefaultValue[$className][$methodName][$name]; + } + + $defaultParams[$name] = $default; + } + + /* Set params according PATH_INFO or GET. */ + if($this->config->requestType == 'PATH_INFO') + { + $this->setParamsByPathInfo($defaultParams); + } + elseif($this->config->requestType == 'GET') + { + $this->setParamsByGET($defaultParams); + } + + /* Call the method. */ + call_user_func_array(array(&$module, $methodName), $this->params); + return $module; + } + + /** + * Set the params by PATH_INFO + * + * @param array $defaultParams the default settings of the params. + * @access public + * @return void + */ + public function setParamsByPathInfo($defaultParams = array()) + { + /* Spit the URI. */ + $items = explode($this->config->requestFix, $this->URI); + $itemCount = count($items); + + $params = array(); + /* The clean mode, only passed in values, no keys. */ + if($this->config->pathType == 'clean') + { + /* The first two item is moduleName and methodName. So the params should begin at 2.*/ + for($i = 2; $i < $itemCount; $i ++) + { + $key = key($defaultParams); // Get key from the $defaultParams. + $params[$key] = $items[$i]; + next($defaultParams); + } + } + /* The full mode, both key and value passed in. */ + elseif($this->config->pathType == 'full') + { + for($i = 2; $i < $itemCount; $i += 2) + { + $key = $items[$i]; + $value = $items[$i + 1]; + $params[$key] = $value; + } + } + $this->params = $this->mergeParams($defaultParams, $params); + } + + /** + * Set the params by GET. + * + * @param array $defaultParams the default settings of the params. + * @access public + * @return void + */ + public function setParamsByGET($defaultParams) + { + /* Unset the moduleVar, methodVar, viewVar and session var, all the left are the params. */ + unset($_GET[$this->config->moduleVar]); + unset($_GET[$this->config->methodVar]); + unset($_GET[$this->config->viewVar]); + unset($_GET[$this->config->sessionVar]); + $this->params = $this->mergeParams($defaultParams, $_GET); + } + + /** + * Merge the params passed in and the default params. Thus the params which have default values needn't pass value, just like a function. + * + * @param array $defaultParams the default params defined by the method. + * @param array $passedParams the params passed in through url. + * @access private + * @return array the merged params. + */ + private function mergeParams($defaultParams, $passedParams) + { + /* If the not strict mode, the keys of passed params and defaaul params msut be the same. */ + if(!isset($this->config->strictParams) or $this->config->strictParams == false) + { + $passedParams = array_values($passedParams); + $i = 0; + foreach($defaultParams as $key => $defaultValue) + { + if(isset($passedParams[$i])) + { + $defaultParams[$key] = $passedParams[$i]; + } + else + { + if($defaultValue === '_NOT_SET') $this->error("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true); + } + $i ++; + } + } + /* If in strict mode, the keys of the passed params must be the same with the default params, but order can be different. */ + else + { + foreach($defaultParams as $key => $defaultValue) + { + if(isset($passedParams[$key])) + { + $defaultParams[$key] = $passedParams[$key]; + } + else + { + if($defaultValue === '_NOT_SET') $this->error("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true); + } + } + } + return $defaultParams; + } + + /** + * Get the $moduleName var. + * + * @access public + * @return string + */ + public function getModuleName() + { + return $this->moduleName; + } + + /** + * Get the $controlFile var. + * + * @access public + * @return string + */ + public function getControlFile() + { + return $this->controlFile; + } + + /** + * Get the $methodName var. + * + * @access public + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * Get the $param var. + * + * @access public + * @return string + */ + public function getParams() + { + return $this->params; + } + + //-------------------- Tool methods.------------------// + + /** + * The error handler. + * + * @param string $message error message + * @param string $file the file error occers + * @param int $line the line error occers + * @param bool $exit exit the program or not + * @access public + * @return void + */ + public function error($message, $file, $line, $exit = false) + { + /* Log the error info. */ + $log = "ERROR: $message in $file on line $line"; + if(isset($_SERVER['SCRIPT_URI'])) $log .= ", request: $_SERVER[SCRIPT_URI]";; + $trace = debug_backtrace(); + extract($trace[0]); + extract($trace[1]); + $log .= ", last called by $file on $line through function $function."; + error_log($log); + + /* If exit, output the error. */ + if($exit) + { + if($this->config->debug) die("$log"); + die(); + } + } + + /** + * Load a class file. + * + * First search in $appLibRoot, then $coreLibRoot. + * + * @param string $className the class name + * @param bool $static statis class or not + * @access public + * @return object|bool the instance of the class or just true. + */ + public function loadClass($className, $static = false) + { + $className = strtolower($className); + + /* Search in $appLibRoot. */ + $classFile = $this->appLibRoot . $className; + if(is_dir($classFile)) $classFile .= $this->pathFix . $className; + $classFile .= '.class.php'; + + if(!helper::import($classFile)) + { + /* Search in $coreLibRoot. */ + $classFile = $this->coreLibRoot . $className; + if(is_dir($classFile)) $classFile .= $this->pathFix . $className; + $classFile .= '.class.php'; + if(!helper::import($classFile)) $this->error("class file $classFile not found", __FILE__, __LINE__, $exit = true); + } + + /* If staitc, return. */ + if($static) return true; + + /* Instance it. */ + global $$className; + if(!class_exists($className)) $this->error("the class $className not found in $classFile", __FILE__, __LINE__, $exit = true); + if(!is_object($$className)) $$className = new $className(); + return $$className; + } + + /** + * Load config and return it as the global config object. + * + * If the module is common, search in $configRoot, else in $modulePath. + * + * @param string $moduleName module name + * @param bool $exitIfNone exit or not + * @access public + * @return object|bool the config object or false. + */ + public function loadConfig($moduleName, $exitIfNone = true) + { + global $config; + if(!is_object($config)) $config = new config(); + + $extConfigFiles = array(); + + /* Set the main config file and extension config file. */ + if($moduleName == 'common') + { + $mainConfigFile = $this->configRoot . 'config.php'; + } + else + { + $mainConfigFile = $this->getModulePath($moduleName) . 'config.php'; + $extConfigPath = $this->getModuleExtPath($moduleName, 'config'); + $extConfigFiles = helper::ls($extConfigPath, '.php'); + } + + /* Set the files to include. */ + if(!is_file($mainConfigFile)) + { + if($exitIfNone) self::error("config file $mainConfigFile not found", __FILE__, __LINE__, true); + if(empty($extConfigFiles) and !isset($config->system->$moduleName)) return false; // and no extension file or extension in db, exit. + $configFiles = $extConfigFiles; + } + else + { + $configFiles = array_merge(array($mainConfigFile), $extConfigFiles); + } + + static $loadedConfigs = array(); + foreach($configFiles as $configFile) + { + if(in_array($configFile, $loadedConfigs)) continue; + include $configFile; + $loadedConfigs[] = $configFile; + } + + /* Merge from the db configs. */ + if(isset($config->system->$moduleName)) + { + foreach($config->system->$moduleName as $item) + { + if($item->section) $config->{$moduleName}->{$item->section}->{$item->key} = $item->value; + if(!$item->section) $config->{$moduleName}->{$item->key} = $item->value; + } + } + + $this->config = $config; + + return $config; + } + + /** + * Export the config params to the client, thus the client can adjust it's logic according the config. + * + * @access public + * @return void + */ + public function exportConfig() + { + $view->version = $this->config->version; + $view->requestType = $this->config->requestType; + $view->pathType = $this->config->pathType; + $view->requestFix = $this->config->requestFix; + $view->moduleVar = $this->config->moduleVar; + $view->methodVar = $this->config->methodVar; + $view->viewVar = $this->config->viewVar; + $view->sessionVar = $this->config->sessionVar; + echo json_encode($view); + } + + /** + * Load lang and return it as the global lang object. + * + * @param string $moduleName the module name + * @access public + * @return bool|ojbect the lang object or false. + */ + public function loadLang($moduleName) + { + $modulePath = $this->getModulePath($moduleName); + $mainLangFile = $modulePath . 'lang' . $this->pathFix . $this->clientLang . '.php'; + $extLangPath = $this->getModuleExtPath($moduleName, 'lang'); + $extLangFiles = helper::ls($extLangPath . $this->clientLang, '.php'); + + /* Set the files to includ. */ + if(!is_file($mainLangFile)) + { + if(empty($extLangFiles)) return false; // also no extension file. + $langFiles = $extLangFiles; + } + else + { + $langFiles = array_merge(array($mainLangFile), $extLangFiles); + } + + global $lang; + if(!is_object($lang)) $lang = new language(); + + static $loadedLangs = array(); + foreach($langFiles as $langFile) + { + if(in_array($langFile, $loadedLangs)) continue; + include $langFile; + $loadedLangs[] = $langFile; + } + + $this->lang = $lang; + return $lang; + } + + /** + * Connect to database. + * + * @access public + * @return void + */ + public function connectDB() + { + global $config, $dbh, $slaveDBH; + if(!isset($config->installed) or !$config->installed) return; + + if(isset($config->db->host)) $this->dbh = $dbh = $this->connectByPDO($config->db); + if(isset($config->slaveDB->host)) $this->slaveDBH = $slaveDBH = $this->connectByPDO($config->slaveDB); + } + + /** + * Connect database by PDO. + * + * @param object $params the database params. + * @access private + * @return object|bool + */ + private function connectByPDO($params) + { + if(!isset($params->driver)) self::error('no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true); + if(!isset($params->user)) return false; + if($params->driver == 'mysql') + { + $dsn = "mysql:host={$params->host}; port={$params->port}; dbname={$params->name}"; + } + try + { + $dbh = new PDO($dsn, $params->user, $params->password, array(PDO::ATTR_PERSISTENT => $params->persistant)); + $dbh->exec("SET NAMES {$params->encoding}"); + + /* If run on linux, set emulatePrepare and bufferQuery to true. */ + if(!isset($params->emulatePrepare) and PHP_OS == 'Linux') $params->emulatePrepare = true; + if(!isset($params->bufferQuery) and PHP_OS == 'Linux') $params->bufferQuery = true; + + $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if(isset($params->strictMode) and $params->strictMode == false) $dbh->exec("SET @@sql_mode= ''"); + if(isset($params->emulatePrepare)) $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $params->emulatePrepare); + if(isset($params->bufferQuery)) $dbh->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $params->bufferQuery); + + return $dbh; + } + catch (PDOException $exception) + { + self::error($exception->getMessage(), __FILE__, __LINE__, $exit = true); + } + } +} + +/** + * The config class. + * + * @package framework + */ +class config +{ + /** + * Set the value of a member. the member can be the foramt like db.user. + * + * + * set('db.user', 'wwccss'); + * ?> + * + * @param string $key the key of the member + * @param mixed $value the value + * @access public + * @return void + */ + public function set($key, $value) + { + helper::setMember('config', $key, $value); + } +} + +/** + * The lang class. + * + * @package framework + */ +class language +{ + /** + * Set the value of a member. the member can be the foramt like db.user. + * + * + * set('version', '1.0); + * ?> + * + * @param string $key the key of the member, can be father.child + * @param mixed $value the value + * @access public + * @return void + */ + public function set($key, $value) + { + helper::setMember('lang', $key, $value); + } + + /** + * Show a member + * + * @param object $obj the object + * @param string $key the key + * @access public + * @return void + */ + public function show($obj, $key) + { + $obj = (array)$obj; + if(isset($obj[$key])) echo $obj[$key]; else echo ''; + } +} + +/** + * The super object class. + * + * @package framework + */ +class super +{ + /** + * Construct, set the var scope. + * + * @param string $scope the scope, can be server, post, get, cookie, session, global + * @access public + * @return void + */ + public function __construct($scope) + { + $this->scope = $scope; + } + + /** + * Set one member value. + * + * @param string the key + * @param mixed $value the value + * @access public + * @return void + */ + public function set($key, $value) + { + if($this->scope == 'post') + { + $_POST[$key] = $value; + } + elseif($this->scope == 'get') + { + $_GET[$key] = $value; + } + elseif($this->scope == 'server') + { + $_SERVER[$key] = $value; + } + elseif($this->scope == 'cookie') + { + $_COOKIE[$key] = $value; + } + elseif($this->scope == 'session') + { + $_SESSION[$key] = $value; + } + elseif($this->scope == 'env') + { + $_ENV[$key] = $value; + } + elseif($this->scope == 'global') + { + $GLOBAL[$key] = $value; + } + } + + /** + * The magic get method. + * + * @param string $key the key + * @access public + * @return mixed|bool return the value of the key or false. + */ + public function __get($key) + { + if($this->scope == 'post') + { + if(isset($_POST[$key])) return $_POST[$key]; + return false; + } + elseif($this->scope == 'get') + { + if(isset($_GET[$key])) return $_GET[$key]; + return false; + } + elseif($this->scope == 'server') + { + if(isset($_SERVER[$key])) return $_SERVER[$key]; + $key = strtoupper($key); + if(isset($_SERVER[$key])) return $_SERVER[$key]; + return false; + } + elseif($this->scope == 'cookie') + { + if(isset($_COOKIE[$key])) return $_COOKIE[$key]; + return false; + } + elseif($this->scope == 'session') + { + if(isset($_SESSION[$key])) return $_SESSION[$key]; + return false; + } + elseif($this->scope == 'env') + { + if(isset($_ENV[$key])) return $_ENV[$key]; + return false; + } + elseif($this->scope == 'global') + { + if(isset($GLOBALS[$key])) return $GLOBALS[$key]; + return false; + } + else + { + return false; + } + } + + /** + * Print the structure. + * + * @access public + * @return void + */ + public function a() + { + if($this->scope == 'post') a($_POST); + if($this->scope == 'get') a($_GET); + if($this->scope == 'server') a($_SERVER); + if($this->scope == 'cookie') a($_COOKIE); + if($this->scope == 'session') a($_SESSION); + if($this->scope == 'env') a($_ENV); + if($this->scope == 'global') a($GLOBAL); + } +} diff --git a/trunk/lib/api/api.class.php b/trunk/lib/api/api.class.php new file mode 100755 index 0000000000..ec65209b85 --- /dev/null +++ b/trunk/lib/api/api.class.php @@ -0,0 +1,1505 @@ + + * @package API + * @version $Id$ + * @link http://www.zentao.net + */ +class ztclient +{ + public $configFile; + public $zentao; + public $agent; + public $session; + + /** + * The construce function. + * + * @param string $zentaoRoot the zentao root url + * @param string $account the login account + * @param string $password the passwod + * @access public + * @return void + */ + public function __construct($zentaoRoot = '', $account = '', $password = '') + { + $this->agent = new snoopy(); + + /* Assign to $this->zentao. */ + $this->zentao->root = rtrim($zentaoRoot, '/') . '/'; + $this->zentao->account = $account; + $this->zentao->password = md5($password); + + /* Load the remote settings. */ + $config = $this->getRemoteConfig(); + foreach($config as $key => $value) $this->zentao->$key = $value; + + $this->startSession(); + $this->login(); + } + + /** + * Get the settings through getconfig api from remote. + * + * @access private + * @return object + */ + private function getRemoteConfig() + { + $url = $this->zentao->root . '/index.php?mode=getconfig'; + $this->agent->fetchText($url); + $config = json_decode($this->agent->results); + return $config; + } + + /** + * Set the session api. + * + * @access private + * @return string the session api url. + */ + private function setSessionAPI() + { + return $this->setControlAPI('api', 'getsessionid'); + } + + /** + * Set the login api. + * + * @access private + * @return string the login api url. + */ + private function setLoginAPI() + { + return $this->setControlAPI('user', 'login'); + } + + /** + * Set the method api. + * + * @param string $module the module name + * @param string $method the methhod name + * @param string $vars the vars to passwd + * @access private + * @return string the control api string. + */ + private function setControlAPI($module, $method, $vars = '') + { + if($this->zentao->requestType == 'GET') + { + $controlAPI = $this->appendSession($this->zentao->root); + $controlAPI .= "&{$this->zentao->moduleVar}=$module&{$this->zentao->methodVar}=$method&{$this->zentao->viewVar}=json$vars"; + } + elseif($this->zentao->requestType == 'PATH_INFO') + { + $controlAPI = $this->zentao->root . $module . $this->zentao->requestFix . $method . $this->zentao->requestFix; + if($vars) + { + $vars = parse_str($vars); + foreach($vars as $var) $controlAPI .= $var . $this->zentao->requestFix; + } + $controlAPI = rtrim($controlAPI, $this->zentao->requestFix) . '.json'; + $controlAPI = $this->appendSession($controlAPI); + } + return $controlAPI; + } + + /** + * Set the method api. + * + * @param string $module the module name + * @param string $method the methhod name + * @param string $vars the vars to passed + * @access private + * @return string the model api string. + */ + private function setModelAPI($module, $method, $vars = '') + { + $vars = str_replace('&', ',', $vars); + if($this->zentao->requestType == 'GET') + { + $modelAPI = $this->appendSession($this->zentao->root); + $modelAPI .= "&{$this->zentao->moduleVar}=api&{$this->zentao->methodVar}=getModel&{$this->zentao->viewVar}=json"; + $modelAPI .="&module=$module&method=$method¶ms=$vars"; + } + elseif($this->zentao->requestType == 'PATH_INFO') + { + $modelAPI = $this->zentao->root . 'api' . $this->zentao->requestFix . 'getmodel' . $this->zentao->requestFix; + $modelAPI .= $module . $this->zentao->requestFix . $method . $this->zentao->requestFix; + $modelAPI .= $vars; + $modelAPI = rtrim($modelAPI, $this->zentao->requestFix) . '.json'; + $modelAPI = $this->appendSession($modelAPI); + } + return $modelAPI; + } + + /** + * Start session + * + * @access public + * @return void + */ + public function startSession() + { + $this->session = null; + $this->session = $this->httpGet($this->setSessionAPI()); + } + + /** + * Login. + * + * @access public + * @return void + */ + public function login() + { + $loginAPI = $this->setLoginAPI(); + $authHash = md5($this->zentao->password . $this->session->rand); + $loginAPI .= "&account={$this->zentao->account}&password=$authHash"; + $this->httpGet($loginAPI); + } + + /** + * Fetch one method of a module's control. + * + * @param string $module the module name + * @param string $method the methhod name + * @param string $vars the vars to passwd + * @access public + * @return void + */ + public function fetch($module, $method = 'index', $vars = '') + { + return $this->httpGet($this->setControlAPI($module, $method, $vars)); + } + + /** + * Fetch one method of a module's model. + * + * @param string $module the module name + * @param string $method the methhod name + * @param string $vars the vars to passwd + * @access public + * @return void + */ + public function fetchModel($module, $method, $vars = '') + { + return $this->httpGet($this->setModelAPI($module, $method, $vars)); + } + + /** + * Get a api and check it. + * + * @param string $url + * @access private + * @return bool + */ + private function httpGet($url) + { + $this->agent->fetch($url); + $result = json_decode($this->agent->results); + if($result->status != 'success') return false; + if(isset($result->data) and md5($result->data) != $result->md5) return false; + if(isset($result->data)) return json_decode($result->data); + return true; + } + + /** + * Fetch one method of a module's control. + * + * @param string $module the module name + * @param string $method the methhod name + * @param array $vars the vars to passwd + * @access public + * @return void + */ + public function post($module, $method = 'index', $vars = array()) + { + return $this->httpPost($this->setControlAPI($module, $method), $vars); + } + + /** + * Post. + * + * @param string $url + * @access private + * @return bool + */ + private function httpPost($url, $vars) + { + $this->agent->submit($url, $vars); + $result = json_decode($this->agent->results); + if($result->status != 'success') return false; + if(isset($result->data) and md5($result->data) != $result->md5) return false; + if(isset($result->data)) return json_decode($result->data); + return true; + } + + /** + * Append session param to the url. + * + * @param string $url + * @access private + * @return string + */ + private function appendSession($url) + { + if(strrpos($url, '&') === false) $url .= '?'; + if($this->session) $url .= '&' . $this->session->sessionName . '=' . $this->session->sessionID; + return str_replace('?&', '?', $url); + } +} +?> + +Copyright (c): 1999-2008 New Digital Group, all rights reserved +Version: 1.2.4 + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You may contact the author of Snoopy by e-mail at: +monte@ohrt.com + +The latest version of Snoopy can be obtained from: +http://snoopy.sourceforge.net/ + +*************************************************/ + +class Snoopy +{ + /**** Public variables ****/ + + /* user definable vars */ + + var $host = "www.php.net"; // host name we are connecting to + var $port = 80; // port we are connecting to + var $proxy_host = ""; // proxy host to use + var $proxy_port = ""; // proxy port to use + var $proxy_user = ""; // proxy user to use + var $proxy_pass = ""; // proxy password to use + + var $agent = "Snoopy v1.2.4"; // agent we masquerade as + var $referer = ""; // referer info to pass + var $cookies = array(); // array of cookies to pass + // $cookies["username"]="joe"; + var $rawheaders = array(); // array of raw headers to send + // $rawheaders["Content-type"]="text/html"; + + var $maxredirs = 5; // http redirection depth maximum. 0 = disallow + var $lastredirectaddr = ""; // contains address of last redirected address + var $offsiteok = true; // allows redirection off-site + var $maxframes = 0; // frame content depth maximum. 0 = disallow + var $expandlinks = true; // expand links to fully qualified URLs. + // this only applies to fetchlinks() + // submitlinks(), and submittext() + var $passcookies = true; // pass set cookies back through redirects + // NOTE: this currently does not respect + // dates, domains or paths. + + var $user = ""; // user for http authentication + var $pass = ""; // password for http authentication + + // http accept types + var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + + var $results = ""; // where the content is put + + var $error = ""; // error messages sent here + var $response_code = ""; // response code returned from server + var $headers = array(); // headers returned from server sent here + var $maxlength = 500000; // max return data length (body) + var $read_timeout = 0; // timeout on read operations, in seconds + // supported only since PHP 4 Beta 4 + // set to 0 to disallow timeouts + var $timed_out = false; // if a read operation timed out + var $status = 0; // http request status + + var $temp_dir = "/tmp"; // temporary directory that the webserver + // has permission to write to. + // under Windows, this should be C:\temp + + var $curl_path = "/usr/local/bin/curl"; + // Snoopy will use cURL for fetching + // SSL content if a full system path to + // the cURL binary is supplied here. + // set to false if you do not have + // cURL installed. See http://curl.haxx.se + // for details on installing cURL. + // Snoopy does *not* use the cURL + // library functions built into php, + // as these functions are not stable + // as of this Snoopy release. + + /**** Private variables ****/ + + var $_maxlinelen = 4096; // max line length (headers) + + var $_httpmethod = "GET"; // default http request method + var $_httpversion = "HTTP/1.0"; // default http request version + var $_submit_method = "POST"; // default submit method + var $_submit_type = "application/x-www-form-urlencoded"; // default submit type + var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type + var $_redirectaddr = false; // will be set if page fetched is a redirect + var $_redirectdepth = 0; // increments on an http redirect + var $_frameurls = array(); // frame src urls + var $_framedepth = 0; // increments on frame depth + + var $_isproxy = false; // set if using a proxy server + var $_fp_timeout = 30; // timeout for socket connection + +/*======================================================================*\ + Function: fetch + Purpose: fetch the contents of a web page + (and possibly other protocols in the + future like ftp, nntp, gopher, etc.) + Input: $URI the location of the page to fetch + Output: $this->results the output text from the fetch +\*======================================================================*/ + + function fetch($URI) + { + + //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS); + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + if (empty($URI_PARTS["path"])) + $URI_PARTS["path"] = ''; + + switch(strtolower($URI_PARTS["scheme"])) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_httpmethod); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_httpmethod); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: submit + Purpose: submit an http form + Input: $URI the location to post the data + $formvars the formvars to use. + format: $formvars["var"] = "val"; + $formfiles an array of files to submit + format: $formfiles["var"] = "/dir/filename.ext"; + Output: $this->results the text output from the post +\*======================================================================*/ + + function submit($URI, $formvars="", $formfiles="") + { + unset($postdata); + + $postdata = $this->_prepare_post_body($formvars, $formfiles); + + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + if (empty($URI_PARTS["path"])) + $URI_PARTS["path"] = ''; + + switch(strtolower($URI_PARTS["scheme"])) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_submit_method,$this->_submit_type,$postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: fetchlinks + Purpose: fetch the links from a web page + Input: $URI where you are fetching from + Output: $this->results an array of the URLs +\*======================================================================*/ + + function fetchlinks($URI) + { + if ($this->fetch($URI)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_striplinks($this->results[$x]); + } + else + $this->results = $this->_striplinks($this->results); + + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results, $URI); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: fetchform + Purpose: fetch the form elements from a web page + Input: $URI where you are fetching from + Output: $this->results the resulting html form +\*======================================================================*/ + + function fetchform($URI) + { + + if ($this->fetch($URI)) + { + + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_stripform($this->results[$x]); + } + else + $this->results = $this->_stripform($this->results); + + return true; + } + else + return false; + } + + +/*======================================================================*\ + Function: fetchtext + Purpose: fetch the text from a web page, stripping the links + Input: $URI where you are fetching from + Output: $this->results the text from the web page +\*======================================================================*/ + + function fetchtext($URI) + { + if($this->fetch($URI)) + { + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_striptext($this->results[$x]); + } + else + $this->results = $this->_striptext($this->results); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submitlinks + Purpose: grab links from a form submission + Input: $URI where you are submitting from + Output: $this->results an array of the links from the post +\*======================================================================*/ + + function submitlinks($URI, $formvars="", $formfiles="") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + { + $this->results[$x] = $this->_striplinks($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striplinks($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submittext + Purpose: grab text from a form submission + Input: $URI where you are submitting from + Output: $this->results the text from the web page +\*======================================================================*/ + + function submittext($URI, $formvars = "", $formfiles = "") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + { + $this->results[$x] = $this->_striptext($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striptext($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + + + +/*======================================================================*\ + Function: set_submit_multipart + Purpose: Set the form submission content type to + multipart/form-data +\*======================================================================*/ + function set_submit_multipart() + { + $this->_submit_type = "multipart/form-data"; + } + + +/*======================================================================*\ + Function: set_submit_normal + Purpose: Set the form submission content type to + application/x-www-form-urlencoded +\*======================================================================*/ + function set_submit_normal() + { + $this->_submit_type = "application/x-www-form-urlencoded"; + } + + + + +/*======================================================================*\ + Private functions +\*======================================================================*/ + + +/*======================================================================*\ + Function: _striplinks + Purpose: strip the hyperlinks from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _striplinks($document) + { + preg_match_all("'<\s*a\s.*?href\s*=\s* # find ]+)) # if quote found, match up to next matching + # quote, otherwise match up to next space + 'isx",$document,$links); + + + // catenate the non-empty matches from the conditional subpattern + + while(list($key,$val) = each($links[2])) + { + if(!empty($val)) + $match[] = $val; + } + + while(list($key,$val) = each($links[3])) + { + if(!empty($val)) + $match[] = $val; + } + + // return the links + return $match; + } + +/*======================================================================*\ + Function: _stripform + Purpose: strip the form elements from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _stripform($document) + { + preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements); + + // catenate the matches + $match = implode("\r\n",$elements[0]); + + // return the links + return $match; + } + + + +/*======================================================================*\ + Function: _striptext + Purpose: strip the text from an html document + Input: $document document to strip. + Output: $text the resulting text +\*======================================================================*/ + + function _striptext($document) + { + + // I didn't use preg eval (//e) since that is only available in PHP 4.0. + // so, list your entities one by one here. I included some of the + // more common ones. + + $search = array("']*?>.*?'si", // strip out javascript + "'<[\/\!]*?[^<>]*?>'si", // strip out html tags + "'([\r\n])[\s]+'", // strip out white space + "'&(quot|#34|#034|#x22);'i", // replace html entities + "'&(amp|#38|#038|#x26);'i", // added hexadecimal values + "'&(lt|#60|#060|#x3c);'i", + "'&(gt|#62|#062|#x3e);'i", + "'&(nbsp|#160|#xa0);'i", + "'&(iexcl|#161);'i", + "'&(cent|#162);'i", + "'&(pound|#163);'i", + "'&(copy|#169);'i", + "'&(reg|#174);'i", + "'&(deg|#176);'i", + "'&(#39|#039|#x27);'", + "'&(euro|#8364);'i", // europe + "'&a(uml|UML);'", // german + "'&o(uml|UML);'", + "'&u(uml|UML);'", + "'&A(uml|UML);'", + "'&O(uml|UML);'", + "'&U(uml|UML);'", + "'ß'i", + ); + $replace = array( "", + "", + "\\1", + "\"", + "&", + "<", + ">", + " ", + chr(161), + chr(162), + chr(163), + chr(169), + chr(174), + chr(176), + chr(39), + chr(128), + "?", + "?", + "?", + "?", + "?", + "?", + "?", + ); + + $text = preg_replace($search,$replace,$document); + + return $text; + } + +/*======================================================================*\ + Function: _expandlinks + Purpose: expand each link into a fully qualified URL + Input: $links the links to qualify + $URI the full URI to get the base from + Output: $expandedLinks the expanded links +\*======================================================================*/ + + function _expandlinks($links,$URI) + { + + preg_match("/^[^\?]+/",$URI,$match); + + $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]); + $match = preg_replace("|/$|","",$match); + $match_part = parse_url($match); + $match_root = + $match_part["scheme"]."://".$match_part["host"]; + + $search = array( "|^http://".preg_quote($this->host)."|i", + "|^(\/)|i", + "|^(?!http://)(?!mailto:)|i", + "|/\./|", + "|/[^\/]+/\.\./|" + ); + + $replace = array( "", + $match_root."/", + $match."/", + "/", + "/" + ); + + $expandedLinks = preg_replace($search,$replace,$links); + + return $expandedLinks; + } + +/*======================================================================*\ + Function: _httprequest + Purpose: go get the http data from the server + Input: $url the url to fetch + $fp the current open file pointer + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="") + { + $cookie_headers = ''; + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + $headers = $http_method." ".$url." ".$this->_httpversion."\r\n"; + if(!empty($this->agent)) + $headers .= "User-Agent: ".$this->agent."\r\n"; + if(!empty($this->host) && !isset($this->rawheaders['Host'])) { + $headers .= "Host: ".$this->host; + if(!empty($this->port)) + $headers .= ":".$this->port; + $headers .= "\r\n"; + } + if(!empty($this->accept)) + $headers .= "Accept: ".$this->accept."\r\n"; + if(!empty($this->referer)) + $headers .= "Referer: ".$this->referer."\r\n"; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_headers .= 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers .= substr($cookie_headers,0,-2) . "\r\n"; + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers .= $headerKey.": ".$headerVal."\r\n"; + } + if(!empty($content_type)) { + $headers .= "Content-type: $content_type"; + if ($content_type == "multipart/form-data") + $headers .= "; boundary=".$this->_mime_boundary; + $headers .= "\r\n"; + } + if(!empty($body)) + $headers .= "Content-length: ".strlen($body)."\r\n"; + if(!empty($this->user) || !empty($this->pass)) + $headers .= "Authorization: Basic ".base64_encode($this->user.":".$this->pass)."\r\n"; + + //add proxy auth headers + if(!empty($this->proxy_user)) + $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass)."\r\n"; + + + $headers .= "\r\n"; + + // set the read timeout if needed + if ($this->read_timeout > 0) + socket_set_timeout($fp, $this->read_timeout); + $this->timed_out = false; + + fwrite($fp,$headers.$body,strlen($headers.$body)); + + $this->_redirectaddr = false; + unset($this->headers); + + while($currentHeader = fgets($fp,$this->_maxlinelen)) + { + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + if($currentHeader == "\r\n") + break; + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location:|URI:)/i",$currentHeader)) + { + // get URL portion of the redirect + preg_match("/^(Location:|URI:)[ ]+(.*)/i",chop($currentHeader),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$currentHeader)) + { + if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status)) + { + $this->status= $status[1]; + } + $this->response_code = $currentHeader; + } + + $this->headers[] = $currentHeader; + } + + $results = ''; + do { + $_data = fread($fp, $this->maxlength); + if (strlen($_data) == 0) { + break; + } + $results .= $_data; + } while(true); + + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + // check if there is a a redirect meta tag + + if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + return true; + } + +/*======================================================================*\ + Function: _httpsrequest + Purpose: go get the https data from the server using curl + Input: $url the url to fetch + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httpsrequest($url,$URI,$http_method,$content_type="",$body="") + { + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $headers = array(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + // GET ... header not needed for curl + //$headers[] = $http_method." ".$url." ".$this->_httpversion; + if(!empty($this->agent)) + $headers[] = "User-Agent: ".$this->agent; + if(!empty($this->host)) + if(!empty($this->port)) + $headers[] = "Host: ".$this->host.":".$this->port; + else + $headers[] = "Host: ".$this->host; + if(!empty($this->accept)) + $headers[] = "Accept: ".$this->accept; + if(!empty($this->referer)) + $headers[] = "Referer: ".$this->referer; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_str = 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers[] = substr($cookie_str,0,-2); + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers[] = $headerKey.": ".$headerVal; + } + if(!empty($content_type)) { + if ($content_type == "multipart/form-data") + $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary; + else + $headers[] = "Content-type: $content_type"; + } + if(!empty($body)) + $headers[] = "Content-length: ".strlen($body); + if(!empty($this->user) || !empty($this->pass)) + $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass); + + for($curr_header = 0; $curr_header < count($headers); $curr_header++) { + $safer_header = strtr( $headers[$curr_header], "\"", " " ); + $cmdline_params .= " -H \"".$safer_header."\""; + } + + if(!empty($body)) + $cmdline_params .= " -d \"$body\""; + + if($this->read_timeout > 0) + $cmdline_params .= " -m ".$this->read_timeout; + + $headerfile = tempnam($temp_dir, "sno"); + + exec($this->curl_path." -k -D \"$headerfile\"".$cmdline_params." \"".escapeshellcmd($URI)."\"",$results,$return); + + if($return) + { + $this->error = "Error: cURL could not retrieve the document, error $return."; + return false; + } + + + $results = implode("\r\n",$results); + + $result_headers = file("$headerfile"); + + $this->_redirectaddr = false; + unset($this->headers); + + for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++) + { + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader])) + { + // get URL portion of the redirect + preg_match("/^(Location: |URI:)\s+(.*)/",chop($result_headers[$currentHeader]),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$result_headers[$currentHeader])) + $this->response_code = $result_headers[$currentHeader]; + + $this->headers[] = $result_headers[$currentHeader]; + } + + // check if there is a a redirect meta tag + + if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + unlink("$headerfile"); + + return true; + } + +/*======================================================================*\ + Function: setcookies() + Purpose: set cookies for a redirection +\*======================================================================*/ + + function setcookies() + { + for($x=0; $xheaders); $x++) + { + if(preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x],$match)) + $this->cookies[$match[1]] = urldecode($match[2]); + } + } + + +/*======================================================================*\ + Function: _check_timeout + Purpose: checks whether timeout has occurred + Input: $fp file pointer +\*======================================================================*/ + + function _check_timeout($fp) + { + if ($this->read_timeout > 0) { + $fp_status = socket_get_status($fp); + if ($fp_status["timed_out"]) { + $this->timed_out = true; + return true; + } + } + return false; + } + +/*======================================================================*\ + Function: _connect + Purpose: make a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _connect(&$fp) + { + if(!empty($this->proxy_host) && !empty($this->proxy_port)) + { + $this->_isproxy = true; + + $host = $this->proxy_host; + $port = $this->proxy_port; + } + else + { + $host = $this->host; + $port = $this->port; + } + + $this->status = 0; + + if($fp = fsockopen( + $host, + $port, + $errno, + $errstr, + $this->_fp_timeout + )) + { + // socket connection succeeded + + return true; + } + else + { + // socket connection failed + $this->status = $errno; + switch($errno) + { + case -3: + $this->error="socket creation failed (-3)"; + case -4: + $this->error="dns lookup failure (-4)"; + case -5: + $this->error="connection refused or timed out (-5)"; + default: + $this->error="connection failed (".$errno.")"; + } + return false; + } + } +/*======================================================================*\ + Function: _disconnect + Purpose: disconnect a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _disconnect($fp) + { + return(fclose($fp)); + } + + +/*======================================================================*\ + Function: _prepare_post_body + Purpose: Prepare post body according to encoding type + Input: $formvars - form variables + $formfiles - form upload files + Output: post body +\*======================================================================*/ + + function _prepare_post_body($formvars, $formfiles) + { + settype($formvars, "array"); + settype($formfiles, "array"); + $postdata = ''; + + if (count($formvars) == 0 && count($formfiles) == 0) + return; + + switch ($this->_submit_type) { + case "application/x-www-form-urlencoded": + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&"; + } + } else + $postdata .= urlencode($key)."=".urlencode($val)."&"; + } + break; + + case "multipart/form-data": + $this->_mime_boundary = "Snoopy".md5(uniqid(microtime())); + + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n"; + $postdata .= "$cur_val\r\n"; + } + } else { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"; + $postdata .= "$val\r\n"; + } + } + + reset($formfiles); + while (list($field_name, $file_names) = each($formfiles)) { + settype($file_names, "array"); + while (list(, $file_name) = each($file_names)) { + if (!is_readable($file_name)) continue; + + $fp = fopen($file_name, "r"); + $file_content = fread($fp, filesize($file_name)); + fclose($fp); + $base_name = basename($file_name); + + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n"; + $postdata .= "$file_content\r\n"; + } + } + $postdata .= "--".$this->_mime_boundary."--\r\n"; + break; + } + + return $postdata; + } +} +?> diff --git a/trunk/lib/dao/dao.class.php b/trunk/lib/dao/dao.class.php new file mode 100755 index 0000000000..9577dc459c --- /dev/null +++ b/trunk/lib/dao/dao.class.php @@ -0,0 +1,1645 @@ +table. + * + * @var string + * @access public + */ + public $alias; + + /** + * The fields will be returned. + * + * @var string + * @access public + */ + public $fields; + + /** + * The query mode, raw or magic. + * + * This var is used to diff dao::from() with sql::from(). + * + * @var string + * @access public + */ + public $mode; + + /** + * The query method: insert, select, update, delete, replace. + * + * @var string + * @access public + */ + public $method; + + /** + * The queries executed. Every query will be saved in this array. + * + * @var array + * @access public + */ + static public $querys = array(); + + /** + * The errors. + * + * @var array + * @access public + */ + static public $errors = array(); + + /** + * The construct method. + * + * @access public + * @return void + */ + public function __construct() + { + global $app, $config, $lang, $dbh, $slaveDBH; + $this->app = $app; + $this->config = $config; + $this->lang = $lang; + $this->dbh = $dbh; + $this->slaveDBH = $slaveDBH ? $slaveDBH : false; + + $this->reset(); + } + + /** + * Set the $table property. + * + * @param string $table + * @access private + * @return void + */ + private function setTable($table) + { + $this->table = $table; + } + + /** + * Set the $alias property. + * + * @param string $alias + * @access private + * @return void + */ + private function setAlias($alias) + { + $this->alias = $alias; + } + + /** + * Set the $fields property. + * + * @param string $fields + * @access private + * @return void + */ + private function setFields($fields) + { + $this->fields = $fields; + } + + /** + * Reset the vars. + * + * @access private + * @return void + */ + private function reset() + { + $this->setFields(''); + $this->setTable(''); + $this->setAlias(''); + $this->setMode(''); + $this->setMethod(''); + } + + //-------------------- According to the query method, call according method of sql class. --------------------// + + /** + * Set the query mode. If the method if like findByxxx, the mode is magic. Else, the mode is raw. + * + * @param string $mode magic|raw + * @access private + * @return void + */ + private function setMode($mode = '') + { + $this->mode = $mode; + } + + /** + * Set the query method: select|update|insert|delete|replace + * + * @param string $method + * @access private + * @return void + */ + private function setMethod($method = '') + { + $this->method = $method; + } + + /** + * The select method, call sql::select(). + * + * @param string $fields + * @access public + * @return object the dao object self. + */ + public function select($fields = '*') + { + $this->setMode('raw'); + $this->setMethod('select'); + $this->sqlobj = sql::select($fields); + return $this; + } + + /** + * The select method, call sql::update(). + * + * @param string $table + * @access public + * @return object the dao object self. + */ + public function update($table) + { + $this->setMode('raw'); + $this->setMethod('update'); + $this->sqlobj = sql::update($table); + $this->setTable($table); + return $this; + } + + /** + * The delete method, call sql::delete(). + * + * @access public + * @return object the dao object self. + */ + public function delete() + { + $this->setMode('raw'); + $this->setMethod('delete'); + $this->sqlobj = sql::delete(); + return $this; + } + + /** + * The insert method, call sql::insert(). + * + * @param string $table + * @access public + * @return object the dao object self. + */ + public function insert($table) + { + $this->setMode('raw'); + $this->setMethod('insert'); + $this->sqlobj = sql::insert($table); + $this->setTable($table); + return $this; + } + + /** + * The replace method, call sql::replace(). + * + * @param string $table + * @access public + * @return object the dao object self. + */ + public function replace($table) + { + $this->setMode('raw'); + $this->setMethod('replace'); + $this->sqlobj = sql::replace($table); + $this->setTable($table); + return $this; + } + + /** + * Set the from table. + * + * @param string $table + * @access public + * @return object the dao object self. + */ + public function from($table) + { + $this->setTable($table); + if($this->mode == 'raw') $this->sqlobj->from($table); + return $this; + } + + /** + * Set the fields. + * + * @param string $fields + * @access public + * @return object the dao object self. + */ + public function fields($fields) + { + $this->setFields($fields); + return $this; + } + + /** + * Alias a table, equal the AS keyword. (Don't use AS, because it's a php keyword.) + * + * @param string $alias + * @access public + * @return object the dao object self. + */ + public function alias($alias) + { + if(empty($this->alias)) $this->setAlias($alias); + $this->sqlobj->alias($alias); + return $this; + } + + /** + * Set the data to update or insert. + * + * @param object $data the data object or array + * @param bool $autoCompany auto append company field or not + * @access public + * @return object the dao object self. + */ + public function data($data, $autoCompany = true) + { + if(!is_object($data)) $data = (object)$data; + if($autoCompany and isset($this->app->company) and $this->table != TABLE_COMPANY and !isset($data->company)) $data->company = $this->app->company->id; + $this->sqlobj->data($data); + return $this; + } + + //-------------------- The sql related method. --------------------// + + /** + * Get the sql string. + * + * @access public + * @return string the sql string after process. + */ + public function get() + { + return $this->processKeywords($this->processSQL()); + } + + /** + * Print the sql string. + * + * @access public + * @return void + */ + public function printSQL() + { + echo $this->processSQL(); + } + + /** + * Process the sql, replace the table, fields and add the company condition. + * + * @param bool $autoCompany + * @access private + * @return string the sql string after process. + */ + private function processSQL($autoCompany = true) + { + $sql = $this->sqlobj->get(); + + /* If the mode is magic, process the $fields and $table. */ + if($this->mode == 'magic') + { + if($this->fields == '') $this->fields = '*'; + if($this->table == '') $this->app->error('Must set the table name', __FILE__, __LINE__, $exit = true); + $sql = sprintf($this->sqlobj->get(), $this->fields, $this->table); + } + + /* If the method if select, update or delete, set the comapny condition. */ + if(isset($this->app->company) and $autoCompany and $this->table != '' and $this->table != TABLE_COMPANY and $this->method != 'insert' and $this->method != 'replace') + { + /* Get the position to insert company = ?. */ + $wherePOS = strrpos($sql, DAO::WHERE); // The position of WHERE keyword. + $groupPOS = strrpos($sql, DAO::GROUPBY); // The position of GROUP BY keyword. + $havingPOS = strrpos($sql, DAO::HAVING); // The position of HAVING keyword. + $orderPOS = strrpos($sql, DAO::ORDERBY); // The position of ORDERBY keyword. + $limitPOS = strrpos($sql, DAO::LIMIT); // The position of LIMIT keyword. + $splitPOS = $orderPOS ? $orderPOS : $limitPOS; // If $orderPOS, use it instead of $limitPOS. + $splitPOS = $havingPOS? $havingPOS: $splitPOS; // If $havingPOS, use it instead of $orderPOS. + $splitPOS = $groupPOS ? $groupPOS : $splitPOS; // If $groupPOS, use it instead of $havingPOS. + + /* Set the conditon to be appened. */ + $tableName = !empty($this->alias) ? $this->alias : $this->table; + $companyCondition = " $tableName.company = '{$this->app->company->id}' "; + + /* If $spliPOS > 0, split the sql at $splitPOS. */ + if($splitPOS) + { + $firstPart = substr($sql, 0, $splitPOS); + $lastPart = substr($sql, $splitPOS); + if($wherePOS) + { + $sql = $firstPart . " AND $companyCondition " . $lastPart; + } + else + { + $sql = $firstPart . " WHERE $companyCondition " . $lastPart; + } + } + else + { + $sql .= $wherePOS ? " AND $companyCondition" : " WHERE $companyCondition"; + } + } + self::$querys[] = $this->processKeywords($sql); + return $sql; + } + + /** + * Process the sql keywords, replace the constants to normal. + * + * @param string $sql + * @access private + * @return string the sql string. + */ + private function processKeywords($sql) + { + return str_replace(array(DAO::WHERE, DAO::GROUPBY, DAO::HAVING, DAO::ORDERBY, DAO::LIMIT), array('WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT'), $sql); + } + + //-------------------- Query related methods. --------------------// + + /** + * Set the dbh. + * + * You can use like this: $this->dao->dbh($dbh), thus you can handle two database. + * + * @param object $dbh + * @access public + * @return object the dao object self. + */ + public function dbh($dbh) + { + $this->dbh = $dbh; + return $this; + } + + /** + * Query the sql, return the statement object. + * + * @param bool $autoCompany + * @access public + * @return object the PDOStatement object. + */ + public function query($autoCompany = true) + { + if(!empty(dao::$errors)) return new PDOStatement(); // If any error, return an empty statement object to make sure the remain method to execute. + + $sql = $this->processSQL($autoCompany); + try + { + $method = $this->method; + $this->reset(); + + if($this->slaveDBH and $method == 'select') + { + return $this->slaveDBH->query($sql); + } + else + { + return $this->dbh->query($sql); + } + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

    The sql is: $sql

    ", __FILE__, __LINE__, $exit = true); + } + } + + /** + * Page the records, set the limit part auto. + * + * @param object $pager + * @access public + * @return object the dao object self. + */ + public function page($pager) + { + if(!is_object($pager)) return $this; + + /* If the record total is 0, compute it. */ + if($pager->recTotal == 0) + { + /* Get the SELECT, FROM position, thus get the fields, replace it by count(*). */ + $sql = $this->get(); + $selectPOS = strpos($sql, 'SELECT') + strlen('SELECT'); + $fromPOS = strpos($sql, 'FROM'); + $fields = substr($sql, $selectPOS, $fromPOS - $selectPOS ); + $sql = str_replace($fields, ' COUNT(*) AS recTotal ', $sql); + + /* Remove the part after order and limit. */ + $subLength = strlen($sql); + $orderPOS = strripos($sql, 'order'); + $limitPOS = strripos($sql , 'limit'); + if($limitPOS) $subLength = $limitPOS; + if($orderPOS) $subLength = $orderPOS; + $sql = substr($sql, 0, $subLength); + self::$querys[] = $sql; + + /* Get the records count. */ + try + { + $row = $this->dbh->query($sql)->fetch(PDO::FETCH_OBJ); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

    The sql is: $sql

    ", __FILE__, __LINE__, $exit = true); + } + + $pager->setRecTotal($row->recTotal); + $pager->setPageTotal(); + } + $this->sqlobj->limit($pager->limit()); + return $this; + } + + /** + /* Execute the sql. It's different with query(), which return the stmt object. But this not. + * + * @param bool $autoCompany + * @access public + * @return int the modified or deleted records. + */ + public function exec($autoCompany = true) + { + if(!empty(dao::$errors)) return new PDOStatement(); // If any error, return an empty statement object to make sure the remain method to execute. + + $sql = $this->processSQL($autoCompany); + try + { + $this->reset(); + return $this->dbh->exec($sql); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

    The sql is: $sql

    ", __FILE__, __LINE__, $exit = true); + } + } + + //-------------------- Fetch related methods. -------------------// + + /** + * Fetch one record. + * + * @param string $field if the field is set, only return the value of this field, else return this record + * @param bool $autoCompany + * @access public + * @return object|mixed + */ + public function fetch($field = '', $autoCompany = true) + { + if(empty($field)) return $this->query($autoCompany)->fetch(); + $this->setFields($field); + $result = $this->query($autoCompany)->fetch(PDO::FETCH_OBJ); + if($result) return $result->$field; + } + + /** + * Fetch all records. + * + * @param string $keyField the key field, thus the return records is keyed by this field + * @param bool $autoCompany + * @access public + * @return array the records + */ + public function fetchAll($keyField = '', $autoCompany = true) + { + $stmt = $this->query($autoCompany); + if(empty($keyField)) return $stmt->fetchAll(); + $rows = array(); + while($row = $stmt->fetch()) $rows[$row->$keyField] = $row; + return $rows; + } + + /** + * Fetch all records and group them by one field. + * + * @param string $groupField the field to group by + * @param string $keyField the field of key + * @param bool $autoCompany + * @access public + * @return array the records. + */ + public function fetchGroup($groupField, $keyField = '', $autoCompany = true) + { + $stmt = $this->query($autoCompany); + $rows = array(); + while($row = $stmt->fetch()) + { + empty($keyField) ? $rows[$row->$groupField][] = $row : $rows[$row->$groupField][$row->$keyField] = $row; + } + return $rows; + } + + /** + * Fetch array like key=>value. + * + * If the keyFiled and valueField not set, use the first and last in the record. + * + * @param string $keyField + * @param string $valueField + * @param bool $autoCompany + * @access public + * @return array + */ + public function fetchPairs($keyField = '', $valueField = '', $autoCompany = true) + { + $pairs = array(); + $ready = false; + $stmt = $this->query($autoCompany); + while($row = $stmt->fetch(PDO::FETCH_ASSOC)) + { + if(!$ready) + { + if(empty($keyField)) $keyField = key($row); + if(empty($valueField)) + { + end($row); + $valueField = key($row); + } + $ready = true; + } + + $pairs[$row[$keyField]] = $row[$valueField]; + } + return $pairs; + } + + /** + * Return the last insert ID. + * + * @access public + * @return int + */ + public function lastInsertID() + { + return $this->dbh->lastInsertID(); + } + + //-------------------- Magic methods.--------------------// + + /** + * Use it to do some convenient queries. + * + * @param string $funcName the function name to be called + * @param array $funcArgs the params + * @access public + * @return object the dao object self. + */ + public function __call($funcName, $funcArgs) + { + $funcName = strtolower($funcName); + + /* findByxxx, xxx as will be in the where. */ + if(strpos($funcName, 'findby') !== false) + { + $this->setMode('magic'); + $field = str_replace('findby', '', $funcName); + if(count($funcArgs) == 1) + { + $operator = '='; + $value = $funcArgs[0]; + } + else + { + $operator = $funcArgs[0]; + $value = $funcArgs[1]; + } + $this->sqlobj = sql::select('%s')->from('%s')->where($field, $operator, $value); + return $this; + } + /* Fetch10. */ + elseif(strpos($funcName, 'fetch') !== false) + { + $max = str_replace('fetch', '', $funcName); + $stmt = $this->query(); + + $rows = array(); + $key = isset($funcArgs[0]) ? $funcArgs[0] : ''; + $i = 0; + while($row = $stmt->fetch()) + { + $key ? $rows[$row->$key] = $row : $rows[] = $row; + $i ++; + if($i == $max) break; + } + return $rows; + } + /* Others, call the method in sql class. */ + else + { + /* Create the max counts of sql class methods, and then create $arg0, $arg1... */ + for($i = 0; $i < SQL::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i]) ? $funcArgs[$i] : null; + } + $this->sqlobj->$funcName($arg0, $arg1, $arg2); + return $this; + } + } + + //-------------------- Checking.--------------------// + + /** + * Check a filed is satisfied with the check rule. + * + * @param string $fieldName the field to check + * @param string $funcName the check rule + * @access public + * @return object the dao object self. + */ + public function check($fieldName, $funcName) + { + /* If no this field in the data, reuturn. */ + if(!isset($this->sqlobj->data->$fieldName)) return $this; + + /* Set the field label and value. */ + global $lang, $config, $app; + $table = strtolower(str_replace(array($config->db->prefix, '`'), '', $this->table)); + $fieldLabel = isset($lang->$table->$fieldName) ? $lang->$table->$fieldName : $fieldName; + $value = $this->sqlobj->data->$fieldName; + + /* Check unique. */ + if($funcName == 'unique') + { + $args = func_get_args(); + $sql = "SELECT COUNT(*) AS count FROM $this->table WHERE `$fieldName` = " . $this->sqlobj->quote($value); + if($this->table != TABLE_COMPANY) $sql .= " AND company = {$this->app->company->id} "; + if(isset($args[2])) $sql .= ' AND ' . $args[2]; + try + { + $row = $this->dbh->query($sql)->fetch(); + if($row->count != 0) $this->logError($funcName, $fieldName, $fieldLabel, array($value)); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

    The sql is: $sql

    ", __FILE__, __LINE__, $exit = true); + } + } + else + { + /* Create the params. */ + $funcArgs = func_get_args(); + unset($funcArgs[0]); + unset($funcArgs[1]); + + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 2]) ? $funcArgs[$i + 2] : null; + } + $checkFunc = 'check' . $funcName; + if(validater::$checkFunc($value, $arg0, $arg1, $arg2) === false) + { + $this->logError($funcName, $fieldName, $fieldLabel, $funcArgs); + } + } + + return $this; + } + + /** + * Check a field, if satisfied with the condition. + * + * @param string $condition + * @param string $fieldName + * @param string $funcName + * @access public + * @return object the dao object self. + */ + public function checkIF($condition, $fieldName, $funcName) + { + if(!$condition) return $this; + $funcArgs = func_get_args(); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 3]) ? $funcArgs[$i + 3] : null; + } + $this->check($fieldName, $funcName, $arg0, $arg1, $arg2); + return $this; + } + + /** + * Batch check some fileds. + * + * @param string $fields the fields to check, join with , + * @param string $funcName + * @access public + * @return object the dao object self. + */ + public function batchCheck($fields, $funcName) + { + $fields = explode(',', str_replace(' ', '', $fields)); + $funcArgs = func_get_args(); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 2]) ? $funcArgs[$i + 2] : null; + } + foreach($fields as $fieldName) $this->check($fieldName, $funcName, $arg0, $arg1, $arg2); + return $this; + } + + /** + * Batch check fields on the condition is true. + * + * @param string $condition + * @param string $fields + * @param string $funcName + * @access public + * @return object the dao object self. + */ + public function batchCheckIF($condition, $fields, $funcName) + { + if(!$condition) return $this; + $fields = explode(',', str_replace(' ', '', $fields)); + $funcArgs = func_get_args(); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 2]) ? $funcArgs[$i + 2] : null; + } + foreach($fields as $fieldName) $this->check($fieldName, $funcName, $arg0, $arg1, $arg2); + return $this; + } + + /** + * Check the fields according the the database schema. + * + * @param string $skipFields fields to skip checking + * @access public + * @return object the dao object self. + */ + public function autoCheck($skipFields = '') + { + $fields = $this->getFieldsType(); + $skipFields = ",$skipFields,"; + + foreach($fields as $fieldName => $validater) + { + if(strpos($skipFields, $fieldName) !== false) continue; // skip it. + if(!isset($this->sqlobj->data->$fieldName)) continue; + if($validater['rule'] == 'skip') continue; + $options = array(); + if(isset($validater['options'])) $options = array_values($validater['options']); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($options[$i]) ? $options[$i] : null; + } + $this->check($fieldName, $validater['rule'], $arg0, $arg1, $arg2); + } + return $this; + } + + /** + * Log the error. + * + * For the error notice, see module/common/lang. + * + * @param string $checkType the check rule + * @param string $fieldName the field name + * @param string $fieldLabel the field label + * @param array $funcArgs the args + * @access public + * @return void + */ + public function logError($checkType, $fieldName, $fieldLabel, $funcArgs = array()) + { + global $lang; + $error = $lang->error->$checkType; + $replaces = array_merge(array($fieldLabel), $funcArgs); // the replace values. + + /* Just a string, cycle the $replaces. */ + if(!is_array($error)) + { + foreach($replaces as $replace) + { + $pos = strpos($error, '%s'); + if($pos === false) break; + $error = substr($error, 0, $pos) . $replace . substr($error, $pos + 2); + } + } + /* If the error define is an array, select the one which %s counts match the $replaces. */ + else + { + /* Remove the empty items. */ + foreach($replaces as $key => $value) if(is_null($value)) unset($replaces[$key]); + $replacesCount = count($replaces); + foreach($error as $errorString) + { + if(substr_count($errorString, '%s') == $replacesCount) + { + $error = vsprintf($errorString, $replaces); + } + } + } + dao::$errors[$fieldName][] = $error; + } + + /** + * Judge any error or not. + * + * @access public + * @return bool + */ + public function isError() + { + return !empty(dao::$errors); + } + + /** + * Get the errors. + * + * @param boolean $join + * @access public + * @return array + */ + public function getError($join = false) + { + $errors = dao::$errors; + dao::$errors = array(); // Must clear it. + + if(!$join) return $errors; + + if(is_array($errors)) + { + $message = ''; + foreach($errors as $item) + { + is_array($item) ? $message .= join('\n', $item) . '\n' : $message .= $item . '\n'; + } + return $message; + } + } + + /** + * Get the defination of fields of the table. + * + * @access private + * @return array + */ + private function getFieldsType() + { + try + { + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + $sql = "DESC $this->table"; + $rawFields = $this->dbh->query($sql)->fetchAll(); + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

    The sql is: $sql

    ", __FILE__, __LINE__, $exit = true); + } + + foreach($rawFields as $rawField) + { + $firstPOS = strpos($rawField->type, '('); + $type = substr($rawField->type, 0, $firstPOS > 0 ? $firstPOS : strlen($rawField->type)); + $type = str_replace(array('big', 'small', 'medium', 'tiny', 'var'), '', $type); + $field = array(); + + if($type == 'enum' or $type == 'set') + { + $rangeBegin = $firstPOS + 2; // Remove the first quote. + $rangeEnd = strrpos($rawField->type, ')') - 1; // Remove the last quote. + $range = substr($rawField->type, $rangeBegin, $rangeEnd - $rangeBegin); + $field['rule'] = 'reg'; + $field['options']['reg'] = '/' . str_replace("','", '|', $range) . '/'; + } + elseif($type == 'char') + { + $begin = $firstPOS + 1; + $end = strpos($rawField->type, ')', $begin); + $length = substr($rawField->type, $begin, $end - $begin); + $field['rule'] = 'length'; + $field['options']['max'] = $length; + $field['options']['min'] = 0; + } + elseif($type == 'int') + { + $field['rule'] = 'int'; + } + elseif($type == 'float' or $type == 'double') + { + $field['rule'] = 'float'; + } + elseif($type == 'date') + { + $field['rule'] = 'date'; + } + else + { + $field['rule'] = 'skip'; + } + $fields[$rawField->field] = $field; + } + return $fields; + } +} + +/** + * The SQL class. + * + * @package framework + */ +class sql +{ + /** + * The max count of params of all methods. + * + */ + const MAX_ARGS = 3; + + /** + * The sql string. + * + * @var string + * @access private + */ + private $sql = ''; + + /** + * The global $dbh. + * + * @var object + * @access protected + */ + protected $dbh; + + /** + * The data to update or insert. + * + * @var mix + * @access protected + */ + public $data; + + /** + * Is the first time to call set. + * + * @var bool + * @access private; + */ + private $isFirstSet = true; + + /** + * If in the logic of judge condition or not. + * + * @var bool + * @access private; + */ + private $inCondition = false; + + /** + * The condition is true or not. + * + * @var bool + * @access private; + */ + private $conditionIsTrue = false; + + /** + * Magic quote or not. + * + * @var bool + * @access public + */ + public $magicQuote; + + /** + * The construct function. user factory() to instance it. + * + * @param string $table + * @access private + * @return void + */ + private function __construct($table = '') + { + global $dbh; + $this->dbh = $dbh; + $this->magicQuote = get_magic_quotes_gpc(); + } + + /** + * The factory method. + * + * @param string $table + * @access public + * @return object the sql object. + */ + public function factory($table = '') + { + return new sql($table); + } + + /** + * The sql is select. + * + * @param string $field + * @access public + * @return object the sql object. + */ + public function select($field = '*') + { + $sqlobj = self::factory(); + $sqlobj->sql = "SELECT $field "; + return $sqlobj; + } + + /** + * The sql is update. + * + * @param string $table + * @access public + * @return object the sql object. + */ + public function update($table) + { + $sqlobj = self::factory(); + $sqlobj->sql = "UPDATE $table SET "; + return $sqlobj; + } + + /** + * The sql is insert. + * + * @param string $table + * @access public + * @return object the sql object. + */ + public function insert($table) + { + $sqlobj = self::factory(); + $sqlobj->sql = "INSERT INTO $table SET "; + return $sqlobj; + } + + /** + * The sql is replace. + * + * @param string $table + * @access public + * @return object the sql object. + */ + public function replace($table) + { + $sqlobj = self::factory(); + $sqlobj->sql = "REPLACE $table SET "; + return $sqlobj; + } + + /** + * The sql is delete. + * + * @access public + * @return object the sql object. + */ + public function delete() + { + $sqlobj = self::factory(); + $sqlobj->sql = "DELETE "; + return $sqlobj; + } + + /** + * Join the data items by key = value. + * + * @param object $data + * @access public + * @return object the sql object. + */ + public function data($data) + { + $this->data = $data; + foreach($data as $field => $value) $this->sql .= "`$field` = " . $this->quote($value) . ','; + $this->sql = rtrim($this->sql, ','); // Remove the last ','. + return $this; + } + + /** + * Aadd an '(' at left. + * + * @param int $count + * @access public + * @return ojbect the sql object. + */ + public function markLeft($count = 1) + { + $this->sql .= str_repeat('(', $count); + return $this; + } + + /** + * Add an ')' ad right. + * + * @param int $count + * @access public + * @return object the sql object. + */ + public function markRight($count = 1) + { + $this->sql .= str_repeat(')', $count); + return $this; + } + + /** + * The set part. + * + * @param string $set + * @access public + * @return object the sql object. + */ + public function set($set) + { + if($this->isFirstSet) + { + $this->sql .= " $set "; + $this->isFirstSet = false; + } + else + { + $this->sql .= ", $set"; + } + return $this; + } + + /** + * Create the from part. + * + * @param string $table + * @access public + * @return object the sql object. + */ + public function from($table) + { + $this->sql .= "FROM $table"; + return $this; + } + + /** + * Create the Alias part. + * + * @param string $alias + * @access public + * @return object the sql object. + */ + public function alias($alias) + { + $this->sql .= " AS $alias "; + return $this; + } + + /** + * Create the left join part. + * + * @param string $table + * @access public + * @return object the sql object. + */ + public function leftJoin($table) + { + $this->sql .= " LEFT JOIN $table"; + return $this; + } + + /** + * Create the on part. + * + * @param string $condition + * @access public + * @return object the sql object. + */ + public function on($condition) + { + $this->sql .= " ON $condition "; + return $this; + } + + /** + * Begin condition judge. + * + * @param bool $condition + * @access public + * @return object the sql object. + */ + public function beginIF($condition) + { + $this->inCondition = true; + $this->conditionIsTrue = $condition; + return $this; + } + + /** + * End the condition judge. + * + * @access public + * @return object the sql object. + */ + public function fi() + { + $this->inCondition = false; + $this->conditionIsTrue = false; + return $this; + } + + /** + * Create the where part. + * + * @param string $arg1 the field name + * @param string $arg2 the operator + * @param string $arg3 the value + * @access public + * @return object the sql object. + */ + public function where($arg1, $arg2 = null, $arg3 = null) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + if($arg3 !== null) + { + $value = $this->quote($arg3); + $condition = "`$arg1` $arg2 " . $this->quote($arg3); + } + else + { + $condition = $arg1; + } + + $this->sql .= ' ' . DAO::WHERE ." $condition "; + return $this; + } + + /** + * Create the AND part. + * + * @param string $condition + * @access public + * @return object the sql object. + */ + public function andWhere($condition) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " AND $condition "; + return $this; + } + + /** + * Create the OR part. + * + * @param bool $condition + * @access public + * @return object the sql object. + */ + public function orWhere($condition) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " OR $condition "; + return $this; + } + + /** + * Create the '='. + * + * @param string $value + * @access public + * @return object the sql object. + */ + public function eq($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " = " . $this->quote($value); + return $this; + } + + /** + * Create '!='. + * + * @param string $value + * @access public + * @return void the sql object. + */ + public function ne($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " != " . $this->quote($value); + return $this; + } + + /** + * Create '>'. + * + * @param string $value + * @access public + * @return object the sql object. + */ + public function gt($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " > " . $this->quote($value); + return $this; + } + + /** + * Create '>='. + * + * @param string $value + * @access public + * @return object the sql object. + */ + public function ge($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " >= " . $this->quote($value); + return $this; + } + + /** + * Create '<'. + * + * @param mixed $value + * @access public + * @return object the sql object. + */ + public function lt($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " < " . $this->quote($value); + return $this; + } + + /** + * Create '<='. + * + * @param mixed $value + * @access public + * @return object the sql object. + */ + public function le($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " <= " . $this->quote($value); + return $this; + } + + /** + * Create "between and" + * + * @param string $min + * @param string $max + * @access public + * @return object the sql object. + */ + public function between($min, $max) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $min = $this->quote($min); + $max = $this->quote($max); + $this->sql .= " BETWEEN $min AND $max "; + return $this; + } + + /** + * Create in part. + * + * @param string|array $ids list string by ',' or an array + * @access public + * @return object the sql object. + */ + public function in($ids) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= helper::dbIN($ids); + return $this; + } + + /** + * Create not in part. + * + * @param string|array $ids list string by ',' or an array + * @access public + * @return object the sql object. + */ + public function notin($ids) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= ' NOT ' . helper::dbIN($ids); + return $this; + } + + /** + * Create the like by part. + * + * @param string $string + * @access public + * @return object the sql object. + */ + public function like($string) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " LIKE " . $this->quote($string); + return $this; + } + + /** + * Create the not like by part. + * + * @param string $string + * @access public + * @return object the sql object. + */ + public function notLike($string) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= "NOT LIKE " . $this->quote($string); + return $this; + } + + /** + * Create the find_in_set by part. + * + * @param int $str + * @param int $strList + * @access public + * @return object the sql object. + */ + public function findInSet($str, $strList) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= "FIND_IN_SET(" . $str . "," . $strList . ")"; + } + + /** + * Create the order by part. + * + * @param string $order + * @access public + * @return object the sql object. + */ + public function orderBy($order) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + + $order = str_replace(array('|', '', '_'), ' ', $order); + $order = str_replace('left', '`left`', $order); // process the left to `left`. + $this->sql .= ' ' . DAO::ORDERBY . " $order"; + return $this; + } + + /** + * Create the limit part. + * + * @param string $limit + * @access public + * @return object the sql object. + */ + public function limit($limit) + { + if(empty($limit)) return $this; + stripos($limit, 'limit') !== false ? $this->sql .= " $limit " : $this->sql .= ' ' . DAO::LIMIT . " $limit "; + return $this; + } + + /** + * Create the groupby part. + * + * @param string $groupBy + * @access public + * @return object the sql object. + */ + public function groupBy($groupBy) + { + $this->sql .= ' ' . DAO::GROUPBY . " $groupBy"; + return $this; + } + + /** + * Create the having part. + * + * @param string $having + * @access public + * @return object the sql object. + */ + public function having($having) + { + $this->sql .= ' ' . DAO::HAVING . " $having"; + return $this; + } + + /** + * Get the sql string. + * + * @access public + * @return string + */ + public function get() + { + return $this->sql; + } + + /** + * Uuote a var. + * + * @param mixed $value + * @access public + * @return mixed + */ + public function quote($value) + { + if($this->magicQuote) $value = stripslashes($value); + return $this->dbh->quote($value); + } +} diff --git a/trunk/lib/file/file.class.php b/trunk/lib/file/file.class.php new file mode 100644 index 0000000000..5c5ea711a7 --- /dev/null +++ b/trunk/lib/file/file.class.php @@ -0,0 +1,184 @@ +copyDir($nextFrom, $nextTo); + } + } + return $copiedFiles; + } + + /** + * Remove a dir. + * + * @param string $dir + * @access public + * @return bool + */ + public function removeDir($dir) + { + $dir = realpath($dir) . '/'; + if($dir == '/') return false; + + if(!is_writable($dir)) return false; + if(!is_dir($dir)) return true; + + $entries = scandir($dir); + foreach($entries as $entry) + { + if($entry == '.' or $entry == '..' or $entry == '.svn') continue; + + $fullEntry = $dir . $entry; + if(is_file($fullEntry)) + { + unlink($fullEntry); + } + else + { + $this->removeDir($fullEntry); + } + } + if(!@rmdir($dir)) return false; + return true; + } + + /** + * Get files under a directory recursive. + * + * @param string $dir + * @param array $exceptions + * @access private + * @return array + */ + public function readDir($dir, $exceptions = array()) + { + static $files = array(); + + if(!is_dir($dir)) return $files; + + $dir = realpath($dir) . '/'; + $entries = scandir($dir); + + foreach($entries as $entry) + { + if($entry == '.' or $entry == '..' or $entry == '.svn') continue; + if(in_array($entry, $exceptions)) continue; + + $fullEntry = $dir . $entry; + if(is_file($fullEntry)) + { + $files[] = $dir . $entry; + } + else + { + $nextDir = $dir . $entry; + $this->readDir($nextDir); + } + } + return $files; + } + + /** + * Make a dir. + * + * @param string $dir + * @access public + * @return bool + */ + public function mkdir($dir) + { + return mkdir($dir); + } + + /** + * Remove a file + * + * @param string $file + * @access public + * @return bool + */ + public function removeFile($file) + { + if(!file_exists($file)) return true; + return @unlink($file); + } + + /** + * Batch remove files. use glob function. + * + * @param string $patern + * @access public + * @return avoid + */ + public function batchRemoveFile($patern) + { + $files = glob($patern); + foreach($files as $file) @unlink($file); + } + + /** + * Remove a file + * + * @param string from + * @param string to + * @access public + * @return bool + */ + public function copyFile($from, $to) + { + return @copy($from, $to); + } + + /** + * Rename a file or directory. + * + * @param string from + * @param string to + * @access public + * @return bool + */ + public function rename($from, $to) + { + return rename($from, $to); + } +} diff --git a/trunk/lib/filter/filter.class.php b/trunk/lib/filter/filter.class.php new file mode 100755 index 0000000000..1061dcd225 --- /dev/null +++ b/trunk/lib/filter/filter.class.php @@ -0,0 +1,616 @@ + array('min_range' => $args[1], 'max_range' => $args[2])); + } + else + { + $options = array('options' => array('min_range' => $args[1])); + } + + return filter_var($var, FILTER_VALIDATE_INT, $options); + } + else + { + return filter_var($var, FILTER_VALIDATE_INT); + } + } + + /** + * Float checking. + * + * @param float $var + * @param string $decimal + * @static + * @access public + * @return bool + */ + public static function checkFloat($var, $decimal = '.') + { + return filter_var($var, FILTER_VALIDATE_FLOAT, array('options' => array('decimail' => $decimal))); + } + + /** + * Email checking. + * + * @param string $var + * @static + * @access public + * @return bool + */ + public static function checkEmail($var) + { + return filter_var($var, FILTER_VALIDATE_EMAIL); + } + + /** + * URL checking. + * + * The check rule of filter don't support chinese. + * + * @param string $var + * @static + * @access public + * @return bool + */ + public static function checkURL($var) + { + return filter_var($var, FILTER_VALIDATE_URL); + } + + /** + * IP checking. + * + * @param ip $var + * @param string $range all|public|static|private + * @static + * @access public + * @return bool + */ + public static function checkIP($var, $range = 'all') + { + if($range == 'all') return filter_var($var, FILTER_VALIDATE_IP); + if($range == 'public static') return filter_var($var, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); + if($range == 'private') + { + if(filter_var($var, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false) return $var; + return false; + } + } + + /** + * Date checking. Note: 2009-09-31 will be an valid date, because strtotime auto fixed it to 10-01. + * + * @param date $date + * @static + * @access public + * @return bool + */ + public static function checkDate($date) + { + if($date == '0000-00-00') return true; + $stamp = strtotime($date); + if(!is_numeric($stamp)) return false; + return checkdate(date('m', $stamp), date('d', $stamp), date('Y', $stamp)); + } + + /** + * REG checking. + * + * @param string $var + * @param string $reg + * @static + * @access public + * @return bool + */ + public static function checkREG($var, $reg) + { + return filter_var($var, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => $reg))); + } + + /** + * Length checking. + * + * @param string $var + * @param string $max + * @param int $min + * @static + * @access public + * @return bool + */ + public static function checkLength($var, $max, $min = 0) + { + return self::checkInt(strlen($var), $min, $max); + } + + /** + * Not empty checking. + * + * @param mixed $var + * @static + * @access public + * @return bool + */ + public static function checkNotEmpty($var) + { + return !empty($var); + } + + /** + * Empty checking. + * + * @param mixed $var + * @static + * @access public + * @return bool + */ + public static function checkEmpty($var) + { + return empty($var); + } + + /** + * Account checking. + * + * @param string $var + * @static + * @access public + * @return bool + */ + public static function checkAccount($var) + { + return self::checkREG($var, '|^[a-zA-Z0-9_]{1}[a-zA-Z0-9_\.]{1,}[a-zA-Z0-9_]{1}$|'); + } + + /** + * Must equal a value. + * + * @param mixed $var + * @param mixed $value + * @static + * @access public + * @return bool + */ + public static function checkEqual($var, $value) + { + return $var == $value; + } + + /** + * Must greater than a value. + * + * @param mixed $var + * @param mixed $value + * @static + * @access public + * @return bool + */ + public static function checkGT($var, $value) + { + return $var > $value; + } + + /** + * Must greater than or equal a value. + * + * @param mixed $var + * @param mixed $value + * @static + * @access public + * @return bool + */ + public static function checkGE($var, $value) + { + return $var >= $value; + } + + /** + * Call a function to check it. + * + * @param mixed $var + * @param string $func + * @static + * @access public + * @return bool + */ + public static function call($var, $func) + { + return filter_var($var, FILTER_CALLBACK, array('options' => $func)); + } +} + +/** + * fixer class, to fix data types. + * + * @package framework + */ +class fixer +{ + /** + * The data to be fixed. + * + * @var ojbect + * @access private + */ + private $data; + + /** + * The construction function, according the scope, convert it to object. + * + * @param string $scope the scope of the var, should be post|get|server|session|cookie|env + * @access private + * @return void + */ + private function __construct($scope) + { + switch($scope) + { + case 'post': + $this->data = (object)$_POST; + break; + case 'server': + $this->data = (object)$_SERVER; + break; + case 'get': + $this->data = (object)$_GET; + break; + case 'session': + $this->data = (object)$_SESSION; + break; + case 'cookie': + $this->data = (object)$_COOKIE; + break; + case 'env': + $this->data = (object)$_ENV; + break; + case 'file': + $this->data = (object)$_FILES; + break; + + default: + die('scope not supported, should be post|get|server|session|cookie|env'); + } + } + + /** + * The factory function. + * + * @param string $scope + * @access public + * @return object fixer object. + */ + public function input($scope) + { + return new fixer($scope); + } + + /** + * Email fix. + * + * @param string $fieldName + * @access public + * @return object fixer object. + */ + public function cleanEmail($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_EMAIL); + return $this; + } + + /** + * urlenocde. + * + * @param string $fieldName + * @access public + * @return object fixer object. + */ + public function encodeURL($fieldName) + { + $fields = $this->processFields($fieldName); + $args = func_get_args(); + foreach($fields as $fieldName) + { + $this->data->$fieldName = isset($args[1]) ? filter_var($this->data->$fieldName, FILTER_SANITIZE_ENCODED, $args[1]) : filter_var($this->data->$fieldName, FILTER_SANITIZE_ENCODED); + } + return $this; + } + + /** + * Clean the url. + * + * @param string $fieldName + * @access public + * @return object fixer object. + */ + public function cleanURL($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_URL); + return $this; + } + + /** + * Float fixer. + * + * @param string $fieldName + * @access public + * @return object fixer object. + */ + public function cleanFloat($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION|FILTER_FLAG_ALLOW_THOUSAND); + return $this; + } + + /** + * Int fixer. + * + * @param string $fieldName + * @access public + * @return object fixer object. + */ + public function cleanINT($fieldName = '') + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_NUMBER_INT); + return $this; + } + + /** + * Special chars + * + * @param string $fieldName + * @access public + * @return object fixer object + */ + public function specialChars($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = htmlspecialchars($this->data->$fieldName); + return $this; + } + + + /** + * Strip tags + * + * @param string $fieldName + * @param string $allowableTags + * @access public + * @return object fixer object + */ + public function stripTags($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_STRING); + return $this; + } + + /** + * Quote + * + * @param string $fieldName + * @access public + * @return object fixer object + */ + public function quote($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_MAGIC_QUOTES); + return $this; + } + + /** + * Set default value of some fileds. + * + * @param string $fields + * @param mixed $value + * @access public + * @return object fixer object + */ + public function setDefault($fields, $value) + { + $fields = strpos($fields, ',') ? explode(',', str_replace(' ', '', $fields)) : array($fields); + foreach($fields as $fieldName)if(!isset($this->data->$fieldName) or empty($this->data->$fieldName)) $this->data->$fieldName = $value; + return $this; + } + + /** + * Set value of a filed on the condition is true. + * + * @param bool $condition + * @param string $fieldName + * @param string $value + * @access public + * @return object fixer object + */ + public function setIF($condition, $fieldName, $value) + { + if($condition) $this->data->$fieldName = $value; + return $this; + } + + /** + * Set the value of a filed in force. + * + * @param string $fieldName + * @param mixed $value + * @access public + * @return object fixer object + */ + public function setForce($fieldName, $value) + { + $this->data->$fieldName = $value; + return $this; + } + + /** + * Remove a field. + * + * @param string $fieldName + * @access public + * @return object fixer object + */ + public function remove($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) unset($this->data->$fieldName); + return $this; + } + + /** + * Remove a filed on the condition is true. + * + * @param bool $condition + * @param string $fields + * @access public + * @return object fixer object + */ + public function removeIF($condition, $fields) + { + $fields = $this->processFields($fields); + if($condition) foreach($fields as $fieldName) unset($this->data->$fieldName); + return $this; + } + + /** + * Add an item to the data. + * + * @param string $fieldName + * @param mixed $value + * @access public + * @return object fixer object + */ + public function add($fieldName, $value) + { + $this->data->$fieldName = $value; + return $this; + } + + /** + * Add an item to the data on the condition if true. + * + * @param bool $condition + * @param string $fieldName + * @param mixed $value + * @access public + * @return object fixer object + */ + public function addIF($condition, $fieldName, $value) + { + if($condition) $this->data->$fieldName = $value; + return $this; + } + + /** + * Join the field. + * + * @param string $fieldName + * @param string $value + * @access public + * @return object fixer object + */ + public function join($fieldName, $value) + { + if(!isset($this->data->$fieldName) or !is_array($this->data->$fieldName)) return $this; + $this->data->$fieldName = join($value, $this->data->$fieldName); + return $this; + } + + /** + * Call a function to fix it. + * + * @param string $fieldName + * @param string $func + * @access public + * @return object fixer object + */ + public function callFunc($fieldName, $func) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_CALLBACK, array('options' => $func)); + return $this; + } + + /** + * Get the data after fixing. + * + * @param string $fieldName + * @access public + * @return object + */ + public function get($fieldName = '') + { + if(empty($fieldName)) return $this->data; + return $this->data->$fieldName; + } + + /** + * Process fields, if contains ',', split it to array. If not in $data, remove it. + * + * @param string $fields + * @access private + * @return array + */ + private function processFields($fields) + { + $fields = strpos($fields, ',') ? explode(',', str_replace(' ', '', $fields)) : array($fields); + foreach($fields as $key => $fieldName) if(!isset($this->data->$fieldName)) unset($fields[$key]); + return $fields; + } +} diff --git a/trunk/lib/front/front.class.php b/trunk/lib/front/front.class.php new file mode 100644 index 0000000000..729cbf5666 --- /dev/null +++ b/trunk/lib/front/front.class.php @@ -0,0 +1,807 @@ +$title\n"; + } + + /** + * Create a meta. + * + * @param mixed $name the meta name + * @param mixed $value the meta value + * @access public + * @return string + */ + public static function meta($name, $value) + { + return "\n"; + } + + /** + * Create icon tag + * + * @param mixed $url the url of the icon. + * @access public + * @return string + */ + public static function icon($url) + { + return "\n" . + "\n"; + + } + + /** + * Create the rss tag. + * + * @param string $url + * @param string $title + * @static + * @access public + * @return string + */ + public static function rss($url, $title = '') + { + return ""; + } + + /** + * Create tags like
    text + * + * @param string $href the link url. + * @param string $title the link title. + * @param string $target the target window + * @param string $misc other params. + * @param boolean $newline + * @return string + */ + static public function a($href = '', $title = '', $target = "_self", $misc = '', $newline = true) + { + global $config; + if(empty($title)) $title = $href; + $newline = $newline ? "\n" : ''; + + /* if page has onlybody param then add this param in all link. the param hide header and footer. */ + if(strpos($href, 'onlybody=yes') === false and isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes') + { + $onlybody = $config->requestType == 'PATH_INFO' ? "?onlybody=yes" : "&onlybody=yes"; + $href .= $onlybody; + } + + if($target == '_self') return "$title$newline"; + return "$title$newline"; + } + + /** + * Create tags like text + * + * @param string $mail the email address + * @param string $title the email title. + * @return string + */ + static public function mailto($mail = '', $title = '') + { + if(empty($title)) $title = $mail; + return "$title"; + } + + /** + * Create tags like "" + * + * @param string $name the name of the select tag. + * @param array $options the array to create select tag from. + * @param string $selectedItems the item(s) to be selected, can like item1,item2. + * @param string $attrib other params such as multiple, size and style. + * @return string + */ + static public function select($name = '', $options = array(), $selectedItems = "", $attrib = "") + { + $options = (array)($options); + if(!is_array($options) or empty($options)) return false; + + /* The begin. */ + $id = $name; + if(strpos($name, '[') !== false) $id = trim(str_replace(']', '', str_replace('[', '', $name))); + $string = "\n"; + } + + /** + * Create select with optgroup. + * + * @param string $name the name of the select tag. + * @param array $groups the option groups. + * @param string $selectedItems the item(s) to be selected, can like item1,item2. + * @param string $attrib other params such as multiple, size and style. + * @return string + */ + static public function selectGroup($name = '', $groups = array(), $selectedItems = "", $attrib = "") + { + if(!is_array($groups) or empty($groups)) return false; + + /* The begin. */ + $id = $name; + if(strpos($name, '[') !== false) $id = trim(str_replace(']', '', str_replace('[', '', $name))); + $string = "\n"; + } + + /** + * Create tags like "" + * + * @param string $name the name of the radio tag. + * @param array $options the array to create radio tag from. + * @param string $checked the value to checked by default. + * @param string $attrib other attribs. + * @return string + */ + static public function radio($name = '', $options = array(), $checked = '', $attrib = '') + { + $options = (array)($options); + if(!is_array($options) or empty($options)) return false; + + $string = ''; + foreach($options as $key => $value) + { + $string .= "" + * + * @param string $name the name of the checkbox tag. + * @param array $options the array to create checkbox tag from. + * @param string $checked the value to checked by default, can be item1,item2 + * @param string $attrib other attribs. + * @return string + */ + static public function checkbox($name, $options, $checked = "", $attrib = "") + { + $options = (array)($options); + if(!is_array($options) or empty($options)) return false; + $string = ''; + $checked = ",$checked,"; + + foreach($options as $key => $value) + { + $key = str_replace('item', '', $key); + $string .= "" + * + * @param string $scope the scope of select all. + * @param string $type the type of input tag. + * @param boolean $checked if the type is checkbox, set the checked attribute. + * @return string + */ + static public function selectAll($scope = "", $type = "button", $checked = false) + { + $string = << +function selectAll(checker, scope, type) +{ + if(scope) + { + if(type == 'button') + { + $('#' + scope + ' input').each(function() + { + $(this).attr("checked", true) + }); + } + else if(type == 'checkbox') + { + $('#' + scope + ' input').each(function() + { + $(this).attr("checked", checker.checked) + }); + } + } + else + { + if(type == 'button') + { + $('input').each(function() + { + $(this).attr("checked", true) + }); + } + else if(type == 'checkbox') + { + $('input').each(function() + { + $(this).attr("checked", checker.checked) + }); + } + } +} + +EOT; + global $lang; + if($type == 'checkbox') + { + if($checked) + { + $string .= " "; + } + else + { + $string .= " "; + } + } + elseif($type == 'button') + { + $string .= ""; + } + + return $string; + } + + /** + * Create tags like "" + * + * @param string $scope the scope of select reverse. + * @return string + */ + static public function selectReverse($scope = "") + { + $string = << +function selectReverse(scope) +{ + if(scope) + { + $('#' + scope + ' input').each(function() + { + $(this).attr("checked", !$(this).attr("checked")) + }); + } + else + { + $('input').each(function() + { + $(this).attr("checked", !$(this).attr("checked")) + }); + } +} + +EOT; + global $lang; + $string .= ""; + + return $string; + } + + /** + * Create tags like "" + * + * @param string $name the name of the text input tag. + * @param string $value the default value. + * @param string $attrib other attribs. + * @return string + */ + static public function input($name, $value = "", $attrib = "") + { + return "\n"; + } + + /** + * Create tags like "" + * + * @param string $name the name of the text input tag. + * @param string $value the default value. + * @param string $attrib other attribs. + * @return string + */ + static public function hidden($name, $value = "", $attrib = "") + { + return "\n"; + } + + /** + * Create tags like "" + * + * @param string $name the name of the text input tag. + * @param string $value the default value. + * @param string $attrib other attribs. + * @return string + */ + static public function password($name, $value = "", $attrib = "") + { + return "\n"; + } + + /** + * Create tags like "" + * + * @param string $name the name of the textarea tag. + * @param string $value the default value of the textarea tag. + * @param string $attrib other attribs. + * @return string + */ + static public function textarea($name, $value = "", $attrib = "") + { + return "\n"; + } + + /** + * Create tags like "". + * + * @param string $name the name of the file name. + * @param string $attrib other attribs. + * @return string + */ + static public function file($name, $attrib = "") + { + return "\n"; + } + + /** + * Create submit button. + * + * @param string $label the label of the button + * @param string $misc other params + * @static + * @access public + * @return string the submit button tag. + */ + public static function submitButton($label = '', $misc = '') + { + if(empty($label)) + { + global $lang; + $label = $lang->save; + } + return " "; + } + + /** + * Create reset button. + * + * @static + * @access public + * @return string the reset button tag. + */ + public static function resetButton() + { + global $lang; + return " "; + } + + /** + * Create common button. + * + * @param string $label the label of the button + * @param string $misc other params + * @static + * @access public + * @return string the common button tag. + */ + public static function commonButton($label = '', $misc = '') + { + return " "; + } + + /** + * create a button, when click, go to a link. + * + * @param string $label the link title + * @param string $link the link url + * @param string $misc other params + * @static + * @access public + * @return string + */ + public static function linkButton($label = '', $link = '', $misc = '') + { + global $config; + + /* if page has onlybody param then add this param in all link. the param hide header and footer. */ + if(strpos($link, 'onlybody=') === false and isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes') + { + $onlybody = $config->requestType == 'PATH_INFO' ? "?onlybody=yes" : "&onlybody=yes"; + $link .= $onlybody; + } + + return " "; + } + + /** + * Print the star images. + * + * @param float $stars 0 1 1.5 2 2.5 3 3.5 4 4.5 5 + * @access public + * @return void + */ + public static function printStars($stars) + { + $redStars = 0; + $halfStars = 0; + $whiteStars = 5; + if($stars) + { + $redStars = floor($stars); + $halfStars = $stars - $redStars ? 1 : 0; + $whiteStars = 5 - ceil($stars); + } + for($i = 1; $i <= $redStars; $i ++) echo ""; + for($i = 1; $i <= $halfStars; $i ++) echo ""; + for($i = 1; $i <= $whiteStars; $i ++) echo ""; + } +} + +/** + * JS class. + * + * @package front + */ +class js +{ + /** + * Import a js file. + * + * @param string $url + * @param string $version + * @access public + * @return string + */ + public static function import($url, $version = '') + { + if(!$version) $version = filemtime(__FILE__); + echo "\n"; + } + + /** + * The start of javascript. + * + * @static + * @access private + * @return string + */ + static private function start($full = true) + { + if($full) return "\n"; + } + + /** + * Show a alert box. + * + * @param string $message + * @static + * @access public + * @return string + */ + static public function alert($message = '') + { + return self::start() . "alert('" . $message . "')" . self::end() . self::resetForm(); + } + + /** + * Show error info. + * + * @param string|array $message + * @static + * @access public + * @return string + */ + static public function error($message) + { + $alertMessage = ''; + if(is_array($message)) + { + foreach($message as $item) + { + is_array($item) ? $alertMessage .= join('\n', $item) . '\n' : $alertMessage .= $item . '\n'; + } + } + else + { + $alertMessage = $message; + } + return self::alert($alertMessage); + } + + /** + * Reset the submit form. + * + * @static + * @access public + * @return string + */ + static public function resetForm() + { + return self::start() . 'if(window.parent) window.parent.document.body.click();' . self::end(); + } + + /** + * show a confirm box, press ok go to okURL, else go to cancleURL. + * + * @param string $message the text to be showed. + * @param string $okURL the url to go to when press 'ok'. + * @param string $cancleURL the url to go to when press 'cancle'. + * @param string $okTarget the target to go to when press 'ok'. + * @param string $cancleTarget the target to go to when press 'cancle'. + * @return string + */ + static public function confirm($message = '', $okURL = '', $cancleURL = '', $okTarget = "self", $cancleTarget = "self", $Echo = true) + { + $js = self::start(); + + $confirmAction = ''; + if(strtolower($okURL) == "back") + { + $confirmAction = "history.back(-1);"; + } + elseif(!empty($okURL)) + { + $confirmAction = "$okTarget.location = '$okURL';"; + } + + $cancleAction = ''; + if(strtolower($cancleURL) == "back") + { + $cancleAction = "history.back(-1);"; + } + elseif(!empty($cancleURL)) + { + $cancleAction = "$cancleTarget.location = '$cancleURL';"; + } + + $js .= <<webRoot; + } + + $js = self::start(); + if(strtolower($url) == "back") + { + $js .= "history.back(-1);\n"; + } + else + { + $js .= "$target.location='$url';\n"; + } + return $js . self::end(); + } + + /** + * Close current window. + * + * @static + * @access public + * @return string + */ + static public function closeWindow() + { + return self::start(). "window.close();" . self::end(); + } + + /** + * Goto a page after a timer. + * + * @param string $url the url will go to. + * @param string $target the target of the url. + * @param int $time the timer, msec. + * @return string the javascript string. + */ + static public function refresh($url, $target = "self", $time = 3000) + { + $js = self::start(); + $js .= "setTimeout(\"$target.location='$url'\", $time);"; + $js .= self::end(); + return $js; + } + + /** + * Reload a window. + * + * @param string $window the window to reload. + * @return string the javascript string. + */ + static public function reload($window = 'self') + { + $js = self::start(); + $js .= "$window.location.href=$window.location.href"; + $js .= self::end(); + return $js; + } + + /** + * Export the config vars for createLink() js version. + * + * @static + * @access public + * @return void + */ + static public function exportConfigVars() + { + if(!function_exists('json_encode')) return false; + + global $app, $config, $lang; + $defaultViewType = $app->getViewType(); + $themeRoot = $app->getWebRoot() . 'theme/'; + $moduleName = $app->getModuleName(); + $methodName = $app->getMethodName(); + $clientLang = $app->getClientLang(); + $requiredFields = ''; + if(isset($config->$moduleName->$methodName->requiredFields)) $requiredFields = str_replace(' ', '', $config->$moduleName->$methodName->requiredFields); + + $jsConfig->webRoot = $config->webRoot; + $jsConfig->cookieLife = ceil(($config->cookieLife - time()) / 86400); + $jsConfig->requestType = $config->requestType; + $jsConfig->pathType = $config->pathType; + $jsConfig->requestFix = $config->requestFix; + $jsConfig->moduleVar = $config->moduleVar; + $jsConfig->methodVar = $config->methodVar; + $jsConfig->viewVar = $config->viewVar; + $jsConfig->defaultView = $defaultViewType; + $jsConfig->themeRoot = $themeRoot; + $jsConfig->currentModule = $moduleName; + $jsConfig->currentMethod = $methodName; + $jsConfig->clientLang = $clientLang; + $jsConfig->requiredFields = $requiredFields; + $jsConfig->lblShowAll = $lang->showAll; + $jsConfig->lblHideClosed = $lang->hideClosed; + $jsConfig->submitting = $lang->submitting; + $jsConfig->save = $lang->save; + $jsConfig->router = $app->server->PHP_SELF; + + $js = self::start(false); + $js .= 'var config=' . json_encode($jsConfig); + $js .= self::end(); + echo $js; + } + + /** + * Execute some js code. + * + * @param string $code + * @static + * @access public + * @return string + */ + static public function execute($code) + { + $js = self::start(); + $js .= $code; + $js .= self::end(); + echo $js; + } +} + +/** + * css class. + * + * @package front + */ +class css +{ + /** + * Import a css file. + * + * @param string $url + * @param string $version + * @access public + * @return vod + */ + public static function import($url, $version = '') + { + if(!$version) $version = filemtime(__FILE__); + echo "\n"; + } + + /** + * Print a css code. + * + * @param string $css + * @static + * @access public + * @return void + */ + public static function internal($css) + { + echo ""; + } +} diff --git a/trunk/lib/pager/pager.class.php b/trunk/lib/pager/pager.class.php new file mode 100755 index 0000000000..84f3aaddd2 --- /dev/null +++ b/trunk/lib/pager/pager.class.php @@ -0,0 +1,470 @@ +setApp(); + $this->setLang(); + $this->setModuleName(); + $this->setMethodName(); + + $this->setRecTotal($recTotal); + $this->setRecPerPage($recPerPage); + $this->setPageTotal(); + $this->setPageID($pageID); + } + + /** + * The factory function. + * + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return object + */ + public function init($recTotal = 0, $recPerPage = 20, $pageID = 1) + { + return new pager($recTotal, $recPerPage, $pageID); + } + + /** + * Set the recTotal property. + * + * @param int $recTotal + * @access public + * @return void + */ + public function setRecTotal($recTotal = 0) + { + $this->recTotal = (int)$recTotal; + } + + /** + * Set the recTotal property. + * + * @param int $recPerPage + * @access public + * @return void + */ + public function setRecPerPage($recPerPage) + { + /* Set the cookie name. */ + $this->pageCookie = 'pager' . ucfirst($this->app->getModuleName()) . ucfirst($this->app->getMethodName()); + + if(isset($_COOKIE[$this->pageCookie])) $recPerPage = $_COOKIE[$this->pageCookie]; + $this->recPerPage = ($recPerPage > 0) ? $recPerPage : PAGER::DEFAULT_REC_PRE_PAGE; + } + + /** + * Set the pageTotal property. + * + * @access public + * @return void + */ + public function setPageTotal() + { + $this->pageTotal = ceil($this->recTotal / $this->recPerPage); + } + + /** + * Set the page id. + * + * @param int $pageID + * @access public + * @return void + */ + public function setPageID($pageID) + { + if($pageID > 0 and $pageID <= $this->pageTotal) + { + $this->pageID = $pageID; + } + else + { + $this->pageID = 1; + } + } + + /** + * Set the $app property; + * + * @access private + * @return void + */ + private function setApp() + { + global $app; + $this->app = $app; + } + + /** + * Set the $lang property. + * + * @access private + * @return void + */ + private function setLang() + { + global $lang; + $this->lang = $lang; + } + + /** + * Set the $moduleName property. + * + * @access private + * @return void + */ + private function setModuleName() + { + $this->moduleName = $this->app->getModuleName(); + } + + /** + * Set the $methodName property. + * + * @access private + * @return void + */ + private function setMethodName() + { + $this->methodName = $this->app->getMethodName(); + } + + /** + * Get recTotal, recPerpage, pageID from the request params, and add them to params. + * + * @access private + * @return void + */ + private function setParams() + { + $this->params = $this->app->getParams(); + foreach($this->params as $key => $value) + { + if(strtolower($key) == 'rectotal') $this->params[$key] = $this->recTotal; + if(strtolower($key) == 'recperpage') $this->params[$key] = $this->recPerPage; + if(strtolower($key) == 'pageID') $this->params[$key] = $this->pageID; + } + } + + /** + * Create the limit string. + * + * @access public + * @return string + */ + public function limit() + { + $limit = ''; + if($this->pageTotal > 1) $limit = ' limit ' . ($this->pageID - 1) * $this->recPerPage . ", $this->recPerPage"; + return $limit; + } + + /** + * Print the pager's html. + * + * @param string $align + * @param string $type + * @access public + * @return void + */ + public function show($align = 'right', $type = 'full') + { + echo $this->get($align, $type); + } + + /** + * Get the pager html string. + * + * @param string $align + * @param string $type the pager type, full|short|shortest + * @access public + * @return string + */ + public function get($align = 'right', $type = 'full') + { + /* If the RecTotal is zero, return with no record. */ + if($this->recTotal == 0) { return "
    {$this->lang->pager->noRecord}
    "; } + + /* Set the params. */ + $this->setParams(); + + /* Create the prePage and nextpage, all types have them. */ + $pager = $this->createPrePage(); + $pager .= $this->createNextPage(); + + /* The short and full type. */ + if($type !== 'shortest') + { + $pager = $this->createFirstPage() . $pager; + $pager .= $this->createLastPage(); + } + + /* Only the full type . */ + if($type == 'full') + { + $pager = $this->createDigest() . $pager; + $pager .= $this->createGoTo(); + $pager .= $this->createRecPerPageJS(); + } + + return "
    $pager
    "; + } + + /** + * Create the digest code. + * + * @access private + * @return string + */ + private function createDigest() + { + return sprintf($this->lang->pager->digest, $this->recTotal, $this->createRecPerPageList(), $this->pageID, $this->pageTotal); + } + + /** + * Create the first page. + * + * @access private + * @return string + */ + private function createFirstPage() + { + if($this->pageID == 1) return $this->lang->pager->first . ' '; + $this->params['pageID'] = 1; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->first); + } + + /** + * Create the pre page html. + * + * @access private + * @return string + */ + private function createPrePage() + { + if($this->pageID == 1) return $this->lang->pager->pre . ' '; + $this->params['pageID'] = $this->pageID - 1; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->pre); + } + + /** + * Create the next page html. + * + * @access private + * @return string + */ + private function createNextPage() + { + if($this->pageID == $this->pageTotal) return $this->lang->pager->next . ' '; + $this->params['pageID'] = $this->pageID + 1; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->next); + } + + /** + * Create the last page + * + * @access private + * @return string + */ + private function createLastPage() + { + if($this->pageID == $this->pageTotal) return $this->lang->pager->last . ' '; + $this->params['pageID'] = $this->pageTotal; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->last); + } + + /** + * Create the select object of record perpage. + * + * @access private + * @return string + */ + private function createRecPerPageJS() + { + /* Replace the recTotal, recPerPage, pageID to special string, and then replace them with values by JS. */ + $params = $this->params; + foreach($params as $key => $value) + { + if(strtolower($key) == 'rectotal') $params[$key] = '_recTotal_'; + if(strtolower($key) == 'recperpage') $params[$key] = '_recPerPage_'; + if(strtolower($key) == 'pageid') $params[$key] = '_pageID_'; + } + $vars = ''; + foreach($params as $key => $value) $vars .= "$key=$value&"; + $vars = rtrim($vars, '&'); + + $js = << + vars = '$vars'; + pageCookie = '$this->pageCookie'; + function submitPage(mode) + { + pageTotal = parseInt(document.getElementById('_pageTotal').value); + pageID = document.getElementById('_pageID').value; + recPerPage = document.getElementById('_recPerPage').value; + recTotal = document.getElementById('_recTotal').value; + $.cookie(pageCookie, recPerPage, {expires:config.cookieLife, path:config.webRoot}); + if(mode == 'changePageID') + { + if(pageID > pageTotal) pageID = pageTotal; + if(pageID < 1) pageID = 1; + } + else if(mode == 'changeRecPerPage') + { + pageID = 1; + } + + vars = vars.replace('_recTotal_', recTotal) + vars = vars.replace('_recPerPage_', recPerPage) + vars = vars.replace('_pageID_', pageID); + location.href=createLink('$this->moduleName', '$this->methodName', vars); + } + +EOT; + return $js; + } + + /** + /* Create the select list of RecPerPage. + * + * @access private + * @return string + */ + private function createRecPerPageList() + { + for($i = 5; $i <= 50; $i += 5) $range[$i] = $i; + $range[100] = 100; + $range[200] = 200; + $range[500] = 500; + $range[1000] = 1000; + return html::select('_recPerPage', $range, $this->recPerPage, "onchange='submitPage(\"changeRecPerPage\");'"); + } + + /** + * Create the goto part html. + * + * @access private + * @return string + */ + private function createGoTo() + { + $goToHtml = "\n"; + $goToHtml .= "\n"; + $goToHtml .= " \n"; + $goToHtml .= ""; + return $goToHtml; + } +} diff --git a/trunk/lib/pclzip/gnu-lgpl.txt b/trunk/lib/pclzip/gnu-lgpl.txt new file mode 100644 index 0000000000..cbee875ba6 --- /dev/null +++ b/trunk/lib/pclzip/gnu-lgpl.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/trunk/lib/pclzip/pclzip.class.php b/trunk/lib/pclzip/pclzip.class.php new file mode 100644 index 0000000000..e7facc1eaa --- /dev/null +++ b/trunk/lib/pclzip/pclzip.class.php @@ -0,0 +1,5694 @@ +zipname = $p_zipname; + $this->zip_fd = 0; + $this->magic_quotes_status = -1; + + // ----- Return + return; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // create($p_filelist, $p_add_dir="", $p_remove_dir="") + // create($p_filelist, $p_option, $p_option_value, ...) + // Description : + // This method supports two different synopsis. The first one is historical. + // This method creates a Zip Archive. The Zip file is created in the + // filesystem. The files and directories indicated in $p_filelist + // are added in the archive. See the parameters description for the + // supported format of $p_filelist. + // When a directory is in the list, the directory and its content is added + // in the archive. + // In this synopsis, the function takes an optional variable list of + // options. See bellow the supported options. + // Parameters : + // $p_filelist : An array containing file or directory names, or + // a string containing one filename or one directory name, or + // a string containing a list of filenames and/or directory + // names separated by spaces. + // $p_add_dir : A path to add before the real path of the archived file, + // in order to have it memorized in the archive. + // $p_remove_dir : A path to remove from the real path of the file to archive, + // in order to have a shorter path memorized in the archive. + // When $p_add_dir and $p_remove_dir are set, $p_remove_dir + // is removed first, before $p_add_dir is added. + // Options : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_CB_PRE_ADD : + // PCLZIP_CB_POST_ADD : + // Return Values : + // 0 on failure, + // The list of the added files, with a status of the add action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + function create($p_filelist) + { + $v_result=1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Set default values + $v_options = array(); + $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove from the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, + array (PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_ADD => 'optional', + PCLZIP_CB_POST_ADD => 'optional', + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); + if ($v_result != 1) { + return 0; + } + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + else { + + // ----- Get the first argument + $v_options[PCLZIP_OPT_ADD_PATH] = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + } + else if ($v_size > 2) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, + "Invalid number / type of arguments"); + return 0; + } + } + } + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Init + $v_string_list = array(); + $v_att_list = array(); + $v_filedescr_list = array(); + $p_result_list = array(); + + // ----- Look if the $p_filelist is really an array + if (is_array($p_filelist)) { + + // ----- Look if the first element is also an array + // This will mean that this is a file description entry + if (isset($p_filelist[0]) && is_array($p_filelist[0])) { + $v_att_list = $p_filelist; + } + + // ----- The list is a list of string names + else { + $v_string_list = $p_filelist; + } + } + + // ----- Look if the $p_filelist is a string + else if (is_string($p_filelist)) { + // ----- Create a list from the string + $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); + } + + // ----- Invalid variable type for $p_filelist + else { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_filelist"); + return 0; + } + + // ----- Reformat the string list + if (sizeof($v_string_list) != 0) { + foreach ($v_string_list as $v_string) { + if ($v_string != '') { + $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + } + else { + } + } + } + + // ----- For each file in the list check the attributes + $v_supported_attributes + = array ( PCLZIP_ATT_FILE_NAME => 'mandatory' + ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional' + ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional' + ,PCLZIP_ATT_FILE_MTIME => 'optional' + ,PCLZIP_ATT_FILE_CONTENT => 'optional' + ,PCLZIP_ATT_FILE_COMMENT => 'optional' + ); + foreach ($v_att_list as $v_entry) { + $v_result = $this->privFileDescrParseAtt($v_entry, + $v_filedescr_list[], + $v_options, + $v_supported_attributes); + if ($v_result != 1) { + return 0; + } + } + + // ----- Expand the filelist (expand directories) + $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Call the create fct + $v_result = $this->privCreate($v_filedescr_list, $p_result_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Return + return $p_result_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // add($p_filelist, $p_add_dir="", $p_remove_dir="") + // add($p_filelist, $p_option, $p_option_value, ...) + // Description : + // This method supports two synopsis. The first one is historical. + // This methods add the list of files in an existing archive. + // If a file with the same name already exists, it is added at the end of the + // archive, the first one is still present. + // If the archive does not exist, it is created. + // Parameters : + // $p_filelist : An array containing file or directory names, or + // a string containing one filename or one directory name, or + // a string containing a list of filenames and/or directory + // names separated by spaces. + // $p_add_dir : A path to add before the real path of the archived file, + // in order to have it memorized in the archive. + // $p_remove_dir : A path to remove from the real path of the file to archive, + // in order to have a shorter path memorized in the archive. + // When $p_add_dir and $p_remove_dir are set, $p_remove_dir + // is removed first, before $p_add_dir is added. + // Options : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_OPT_ADD_COMMENT : + // PCLZIP_OPT_PREPEND_COMMENT : + // PCLZIP_CB_PRE_ADD : + // PCLZIP_CB_POST_ADD : + // Return Values : + // 0 on failure, + // The list of the added files, with a status of the add action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + function add($p_filelist) + { + $v_result=1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Set default values + $v_options = array(); + $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove form the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, + array (PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_ADD => 'optional', + PCLZIP_CB_POST_ADD => 'optional', + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_ADD_COMMENT => 'optional', + PCLZIP_OPT_PREPEND_COMMENT => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); + if ($v_result != 1) { + return 0; + } + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + else { + + // ----- Get the first argument + $v_options[PCLZIP_OPT_ADD_PATH] = $v_add_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + } + else if ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } + } + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Init + $v_string_list = array(); + $v_att_list = array(); + $v_filedescr_list = array(); + $p_result_list = array(); + + // ----- Look if the $p_filelist is really an array + if (is_array($p_filelist)) { + + // ----- Look if the first element is also an array + // This will mean that this is a file description entry + if (isset($p_filelist[0]) && is_array($p_filelist[0])) { + $v_att_list = $p_filelist; + } + + // ----- The list is a list of string names + else { + $v_string_list = $p_filelist; + } + } + + // ----- Look if the $p_filelist is a string + else if (is_string($p_filelist)) { + // ----- Create a list from the string + $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); + } + + // ----- Invalid variable type for $p_filelist + else { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type '".gettype($p_filelist)."' for p_filelist"); + return 0; + } + + // ----- Reformat the string list + if (sizeof($v_string_list) != 0) { + foreach ($v_string_list as $v_string) { + $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + } + } + + // ----- For each file in the list check the attributes + $v_supported_attributes + = array ( PCLZIP_ATT_FILE_NAME => 'mandatory' + ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional' + ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional' + ,PCLZIP_ATT_FILE_MTIME => 'optional' + ,PCLZIP_ATT_FILE_CONTENT => 'optional' + ,PCLZIP_ATT_FILE_COMMENT => 'optional' + ); + foreach ($v_att_list as $v_entry) { + $v_result = $this->privFileDescrParseAtt($v_entry, + $v_filedescr_list[], + $v_options, + $v_supported_attributes); + if ($v_result != 1) { + return 0; + } + } + + // ----- Expand the filelist (expand directories) + $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Call the create fct + $v_result = $this->privAdd($v_filedescr_list, $p_result_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Return + return $p_result_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : listContent() + // Description : + // This public method, gives the list of the files and directories, with their + // properties. + // The properties of each entries in the list are (used also in other functions) : + // filename : Name of the file. For a create or add action it is the filename + // given by the user. For an extract function it is the filename + // of the extracted file. + // stored_filename : Name of the file / directory stored in the archive. + // size : Size of the stored file. + // compressed_size : Size of the file's data compressed in the archive + // (without the headers overhead) + // mtime : Last known modification date of the file (UNIX timestamp) + // comment : Comment associated with the file + // folder : true | false + // index : index of the file in the archive + // status : status of the action (depending of the action) : + // Values are : + // ok : OK ! + // filtered : the file / dir is not extracted (filtered by user) + // already_a_directory : the file can not be extracted because a + // directory with the same name already exists + // write_protected : the file can not be extracted because a file + // with the same name already exists and is + // write protected + // newer_exist : the file was not extracted because a newer file exists + // path_creation_fail : the file is not extracted because the folder + // does not exist and can not be created + // write_error : the file was not extracted because there was a + // error while writing the file + // read_error : the file was not extracted because there was a error + // while reading the file + // invalid_header : the file was not extracted because of an archive + // format error (bad file header) + // Note that each time a method can continue operating when there + // is an action error on a file, the error is only logged in the file status. + // Return Values : + // 0 on an unrecoverable failure, + // The list of the files in the archive. + // -------------------------------------------------------------------------------- + function listContent() + { + $v_result=1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return(0); + } + + // ----- Call the extracting fct + $p_list = array(); + if (($v_result = $this->privList($p_list)) != 1) + { + unset($p_list); + return(0); + } + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // extract($p_path="./", $p_remove_path="") + // extract([$p_option, $p_option_value, ...]) + // Description : + // This method supports two synopsis. The first one is historical. + // This method extract all the files / directories from the archive to the + // folder indicated in $p_path. + // If you want to ignore the 'root' part of path of the memorized files + // you can indicate this in the optional $p_remove_path parameter. + // By default, if a newer file with the same name already exists, the + // file is not extracted. + // + // If both PCLZIP_OPT_PATH and PCLZIP_OPT_ADD_PATH aoptions + // are used, the path indicated in PCLZIP_OPT_ADD_PATH is append + // at the end of the path value of PCLZIP_OPT_PATH. + // Parameters : + // $p_path : Path where the files and directories are to be extracted + // $p_remove_path : First part ('root' part) of the memorized path + // (if any similar) to remove while extracting. + // Options : + // PCLZIP_OPT_PATH : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_CB_PRE_EXTRACT : + // PCLZIP_CB_POST_EXTRACT : + // Return Values : + // 0 or a negative value on failure, + // The list of the extracted files, with a status of the action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + function extract() + { + $v_result=1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return(0); + } + + // ----- Set default values + $v_options = array(); +// $v_path = "./"; + $v_path = ''; + $v_remove_path = ""; + $v_remove_all_path = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Default values for option + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE; + + // ----- Look for arguments + if ($v_size > 0) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, + array (PCLZIP_OPT_PATH => 'optional', + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_EXTRACT => 'optional', + PCLZIP_CB_POST_EXTRACT => 'optional', + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_BY_NAME => 'optional', + PCLZIP_OPT_BY_EREG => 'optional', + PCLZIP_OPT_BY_PREG => 'optional', + PCLZIP_OPT_BY_INDEX => 'optional', + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional' + ,PCLZIP_OPT_STOP_ON_ERROR => 'optional' + ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Set the arguments + if (isset($v_options[PCLZIP_OPT_PATH])) { + $v_path = $v_options[PCLZIP_OPT_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { + $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { + // ----- Check for '/' in last path char + if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { + $v_path .= '/'; + } + $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + } + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + else { + + // ----- Get the first argument + $v_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_remove_path = $v_arg_list[1]; + } + else if ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } + } + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Trace + + // ----- Call the extracting fct + $p_list = array(); + $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, + $v_remove_all_path, $v_options); + if ($v_result < 1) { + unset($p_list); + return(0); + } + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + + // -------------------------------------------------------------------------------- + // Function : + // extractByIndex($p_index, $p_path="./", $p_remove_path="") + // extractByIndex($p_index, [$p_option, $p_option_value, ...]) + // Description : + // This method supports two synopsis. The first one is historical. + // This method is doing a partial extract of the archive. + // The extracted files or folders are identified by their index in the + // archive (from 0 to n). + // Note that if the index identify a folder, only the folder entry is + // extracted, not all the files included in the archive. + // Parameters : + // $p_index : A single index (integer) or a string of indexes of files to + // extract. The form of the string is "0,4-6,8-12" with only numbers + // and '-' for range or ',' to separate ranges. No spaces or ';' + // are allowed. + // $p_path : Path where the files and directories are to be extracted + // $p_remove_path : First part ('root' part) of the memorized path + // (if any similar) to remove while extracting. + // Options : + // PCLZIP_OPT_PATH : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_EXTRACT_AS_STRING : The files are extracted as strings and + // not as files. + // The resulting content is in a new field 'content' in the file + // structure. + // This option must be used alone (any other options are ignored). + // PCLZIP_CB_PRE_EXTRACT : + // PCLZIP_CB_POST_EXTRACT : + // Return Values : + // 0 on failure, + // The list of the extracted files, with a status of the action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + //function extractByIndex($p_index, options...) + function extractByIndex($p_index) + { + $v_result=1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return(0); + } + + // ----- Set default values + $v_options = array(); +// $v_path = "./"; + $v_path = ''; + $v_remove_path = ""; + $v_remove_all_path = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Default values for option + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE; + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove form the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, + array (PCLZIP_OPT_PATH => 'optional', + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_EXTRACT => 'optional', + PCLZIP_CB_POST_EXTRACT => 'optional', + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional' + ,PCLZIP_OPT_STOP_ON_ERROR => 'optional' + ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Set the arguments + if (isset($v_options[PCLZIP_OPT_PATH])) { + $v_path = $v_options[PCLZIP_OPT_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { + $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { + // ----- Check for '/' in last path char + if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { + $v_path .= '/'; + } + $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + } + if (!isset($v_options[PCLZIP_OPT_EXTRACT_AS_STRING])) { + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE; + } + else { + } + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + else { + + // ----- Get the first argument + $v_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_remove_path = $v_arg_list[1]; + } + else if ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } + } + + // ----- Trace + + // ----- Trick + // Here I want to reuse extractByRule(), so I need to parse the $p_index + // with privParseOptions() + $v_arg_trick = array (PCLZIP_OPT_BY_INDEX, $p_index); + $v_options_trick = array(); + $v_result = $this->privParseOptions($v_arg_trick, sizeof($v_arg_trick), $v_options_trick, + array (PCLZIP_OPT_BY_INDEX => 'optional' )); + if ($v_result != 1) { + return 0; + } + $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX]; + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Call the extracting fct + if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) { + return(0); + } + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // delete([$p_option, $p_option_value, ...]) + // Description : + // This method removes files from the archive. + // If no parameters are given, then all the archive is emptied. + // Parameters : + // None or optional arguments. + // Options : + // PCLZIP_OPT_BY_INDEX : + // PCLZIP_OPT_BY_NAME : + // PCLZIP_OPT_BY_EREG : + // PCLZIP_OPT_BY_PREG : + // Return Values : + // 0 on failure, + // The list of the files which are still present in the archive. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + function delete() + { + $v_result=1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return(0); + } + + // ----- Set default values + $v_options = array(); + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 0) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, + array (PCLZIP_OPT_BY_NAME => 'optional', + PCLZIP_OPT_BY_EREG => 'optional', + PCLZIP_OPT_BY_PREG => 'optional', + PCLZIP_OPT_BY_INDEX => 'optional' )); + if ($v_result != 1) { + return 0; + } + } + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Call the delete fct + $v_list = array(); + if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) { + $this->privSwapBackMagicQuotes(); + unset($v_list); + return(0); + } + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : deleteByIndex() + // Description : + // ***** Deprecated ***** + // delete(PCLZIP_OPT_BY_INDEX, $p_index) should be prefered. + // -------------------------------------------------------------------------------- + function deleteByIndex($p_index) + { + + $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index); + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : properties() + // Description : + // This method gives the properties of the archive. + // The properties are : + // nb : Number of files in the archive + // comment : Comment associated with the archive file + // status : not_exist, ok + // Parameters : + // None + // Return Values : + // 0 on failure, + // An array with the archive properties. + // -------------------------------------------------------------------------------- + function properties() + { + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + $this->privSwapBackMagicQuotes(); + return(0); + } + + // ----- Default properties + $v_prop = array(); + $v_prop['comment'] = ''; + $v_prop['nb'] = 0; + $v_prop['status'] = 'not_exist'; + + // ----- Look if file exists + if (@is_file($this->zipname)) + { + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) + { + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode'); + + // ----- Return + return 0; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + { + $this->privSwapBackMagicQuotes(); + return 0; + } + + // ----- Close the zip file + $this->privCloseFd(); + + // ----- Set the user attributes + $v_prop['comment'] = $v_central_dir['comment']; + $v_prop['nb'] = $v_central_dir['entries']; + $v_prop['status'] = 'ok'; + } + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_prop; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : duplicate() + // Description : + // This method creates an archive by copying the content of an other one. If + // the archive already exist, it is replaced by the new one without any warning. + // Parameters : + // $p_archive : The filename of a valid archive, or + // a valid PclZip object. + // Return Values : + // 1 on success. + // 0 or a negative value on error (error code). + // -------------------------------------------------------------------------------- + function duplicate($p_archive) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Look if the $p_archive is a PclZip object + if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip')) + { + + // ----- Duplicate the archive + $v_result = $this->privDuplicate($p_archive->zipname); + } + + // ----- Look if the $p_archive is a string (so a filename) + else if (is_string($p_archive)) + { + + // ----- Check that $p_archive is a valid zip file + // TBC : Should also check the archive format + if (!is_file($p_archive)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '".$p_archive."'"); + $v_result = PCLZIP_ERR_MISSING_FILE; + } + else { + // ----- Duplicate the archive + $v_result = $this->privDuplicate($p_archive); + } + } + + // ----- Invalid variable + else + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); + $v_result = PCLZIP_ERR_INVALID_PARAMETER; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : merge() + // Description : + // This method merge the $p_archive_to_add archive at the end of the current + // one ($this). + // If the archive ($this) does not exist, the merge becomes a duplicate. + // If the $p_archive_to_add archive does not exist, the merge is a success. + // Parameters : + // $p_archive_to_add : It can be directly the filename of a valid zip archive, + // or a PclZip object archive. + // Return Values : + // 1 on success, + // 0 or negative values on error (see below). + // -------------------------------------------------------------------------------- + function merge($p_archive_to_add) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return(0); + } + + // ----- Look if the $p_archive_to_add is a PclZip object + if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip')) + { + + // ----- Merge the archive + $v_result = $this->privMerge($p_archive_to_add); + } + + // ----- Look if the $p_archive_to_add is a string (so a filename) + else if (is_string($p_archive_to_add)) + { + + // ----- Create a temporary archive + $v_object_archive = new PclZip($p_archive_to_add); + + // ----- Merge the archive + $v_result = $this->privMerge($v_object_archive); + } + + // ----- Invalid variable + else + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); + $v_result = PCLZIP_ERR_INVALID_PARAMETER; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + + + // -------------------------------------------------------------------------------- + // Function : errorCode() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function errorCode() + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + return(PclErrorCode()); + } + else { + return($this->error_code); + } + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : errorName() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function errorName($p_with_code=false) + { + $v_name = array ( PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR', + PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL', + PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL', + PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER', + PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE', + PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG', + PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP', + PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE', + PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL', + PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION', + PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT', + PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL', + PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL', + PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM', + PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', + PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE', + PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE', + PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', + PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION' + ,PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE' + ,PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION' + ); + + if (isset($v_name[$this->error_code])) { + $v_value = $v_name[$this->error_code]; + } + else { + $v_value = 'NoName'; + } + + if ($p_with_code) { + return($v_value.' ('.$this->error_code.')'); + } + else { + return($v_value); + } + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : errorInfo() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function errorInfo($p_full=false) + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + return(PclErrorString()); + } + else { + if ($p_full) { + return($this->errorName(true)." : ".$this->error_string); + } + else { + return($this->error_string." [code ".$this->error_code."]"); + } + } + } + // -------------------------------------------------------------------------------- + + +// -------------------------------------------------------------------------------- +// ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS ***** +// ***** ***** +// ***** THESES FUNCTIONS MUST NOT BE USED DIRECTLY ***** +// -------------------------------------------------------------------------------- + + + + // -------------------------------------------------------------------------------- + // Function : privCheckFormat() + // Description : + // This method check that the archive exists and is a valid zip archive. + // Several level of check exists. (futur) + // Parameters : + // $p_level : Level of check. Default 0. + // 0 : Check the first bytes (magic codes) (default value)) + // 1 : 0 + Check the central directory (futur) + // 2 : 1 + Check each file header (futur) + // Return Values : + // true on success, + // false on error, the error code is set. + // -------------------------------------------------------------------------------- + function privCheckFormat($p_level=0) + { + $v_result = true; + + // ----- Reset the file system cache + clearstatcache(); + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Look if the file exits + if (!is_file($this->zipname)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '".$this->zipname."'"); + return(false); + } + + // ----- Check that the file is readeable + if (!is_readable($this->zipname)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '".$this->zipname."'"); + return(false); + } + + // ----- Check the magic code + // TBC + + // ----- Check the central header + // TBC + + // ----- Check each file header + // TBC + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privParseOptions() + // Description : + // This internal methods reads the variable list of arguments ($p_options_list, + // $p_size) and generate an array with the options and values ($v_result_list). + // $v_requested_options contains the options that can be present and those that + // must be present. + // $v_requested_options is an array, with the option value as key, and 'optional', + // or 'mandatory' as value. + // Parameters : + // See above. + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options=false) + { + $v_result=1; + + // ----- Read the options + $i=0; + while ($i<$p_size) { + + // ----- Check if the option is supported + if (!isset($v_requested_options[$p_options_list[$i]])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '".$p_options_list[$i]."' for this method"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for next option + switch ($p_options_list[$i]) { + // ----- Look for options that request a path value + case PCLZIP_OPT_PATH : + case PCLZIP_OPT_REMOVE_PATH : + case PCLZIP_OPT_ADD_PATH : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE); + $i++; + break; + + case PCLZIP_OPT_TEMP_FILE_THRESHOLD : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + return PclZip::errorCode(); + } + + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); + return PclZip::errorCode(); + } + + // ----- Check the value + $v_value = $p_options_list[$i+1]; + if ((!is_integer($v_value)) || ($v_value<0)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + return PclZip::errorCode(); + } + + // ----- Get the value (and convert it in bytes) + $v_result_list[$p_options_list[$i]] = $v_value*1048576; + $i++; + break; + + case PCLZIP_OPT_TEMP_FILE_ON : + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); + return PclZip::errorCode(); + } + + $v_result_list[$p_options_list[$i]] = true; + break; + + case PCLZIP_OPT_TEMP_FILE_OFF : + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'"); + return PclZip::errorCode(); + } + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'"); + return PclZip::errorCode(); + } + + $v_result_list[$p_options_list[$i]] = true; + break; + + case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if ( is_string($p_options_list[$i+1]) + && ($p_options_list[$i+1] != '')) { + $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE); + $i++; + } + else { + } + break; + + // ----- Look for options that request an array of string for value + case PCLZIP_OPT_BY_NAME : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i+1])) { + $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i+1]; + } + else if (is_array($p_options_list[$i+1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; + } + else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that request an EREG or PREG expression + case PCLZIP_OPT_BY_EREG : + // ereg() is deprecated starting with PHP 5.3. Move PCLZIP_OPT_BY_EREG + // to PCLZIP_OPT_BY_PREG + $p_options_list[$i] = PCLZIP_OPT_BY_PREG; + case PCLZIP_OPT_BY_PREG : + //case PCLZIP_OPT_CRYPT : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i+1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; + } + else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that takes a string + case PCLZIP_OPT_COMMENT : + case PCLZIP_OPT_ADD_COMMENT : + case PCLZIP_OPT_PREPEND_COMMENT : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, + "Missing parameter value for option '" + .PclZipUtilOptionText($p_options_list[$i]) + ."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i+1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; + } + else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, + "Wrong parameter value for option '" + .PclZipUtilOptionText($p_options_list[$i]) + ."'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that request an array of index + case PCLZIP_OPT_BY_INDEX : + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_work_list = array(); + if (is_string($p_options_list[$i+1])) { + + // ----- Remove spaces + $p_options_list[$i+1] = strtr($p_options_list[$i+1], ' ', ''); + + // ----- Parse items + $v_work_list = explode(",", $p_options_list[$i+1]); + } + else if (is_integer($p_options_list[$i+1])) { + $v_work_list[0] = $p_options_list[$i+1].'-'.$p_options_list[$i+1]; + } + else if (is_array($p_options_list[$i+1])) { + $v_work_list = $p_options_list[$i+1]; + } + else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Value must be integer, string or array for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Reduce the index list + // each index item in the list must be a couple with a start and + // an end value : [0,3], [5-5], [8-10], ... + // ----- Check the format of each item + $v_sort_flag=false; + $v_sort_value=0; + for ($j=0; $j= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1]; + $i++; + break; + + // ----- Look for options that request a call-back + case PCLZIP_CB_PRE_EXTRACT : + case PCLZIP_CB_POST_EXTRACT : + case PCLZIP_CB_PRE_ADD : + case PCLZIP_CB_POST_ADD : + /* for futur use + case PCLZIP_CB_PRE_DELETE : + case PCLZIP_CB_POST_DELETE : + case PCLZIP_CB_PRE_LIST : + case PCLZIP_CB_POST_LIST : + */ + // ----- Check the number of parameters + if (($i+1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_function_name = $p_options_list[$i+1]; + + // ----- Check that the value is a valid existing function + if (!function_exists($v_function_name)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '".$v_function_name."()' is not an existing function for option '".PclZipUtilOptionText($p_options_list[$i])."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Set the attribute + $v_result_list[$p_options_list[$i]] = $v_function_name; + $i++; + break; + + default : + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, + "Unknown parameter '" + .$p_options_list[$i]."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Next options + $i++; + } + + // ----- Look for mandatory options + if ($v_requested_options !== false) { + for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + // ----- Look if present + if (!isset($v_result_list[$key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")"); + + // ----- Return + return PclZip::errorCode(); + } + } + } + } + + // ----- Look for default values + if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { + + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privOptionDefaultThreshold() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privOptionDefaultThreshold(&$p_options) + { + $v_result=1; + + if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) + || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) { + return $v_result; + } + + // ----- Get 'memory_limit' configuration value + $v_memory_limit = ini_get('memory_limit'); + $v_memory_limit = trim($v_memory_limit); + $last = strtolower(substr($v_memory_limit, -1)); + + if($last == 'g') + //$v_memory_limit = $v_memory_limit*1024*1024*1024; + $v_memory_limit = $v_memory_limit*1073741824; + if($last == 'm') + //$v_memory_limit = $v_memory_limit*1024*1024; + $v_memory_limit = $v_memory_limit*1048576; + if($last == 'k') + $v_memory_limit = $v_memory_limit*1024; + + $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit*PCLZIP_TEMPORARY_FILE_RATIO); + + + // ----- Sanity check : No threshold if value lower than 1M + if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) { + unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privFileDescrParseAtt() + // Description : + // Parameters : + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options=false) + { + $v_result=1; + + // ----- For each file in the list check the attributes + foreach ($p_file_list as $v_key => $v_value) { + + // ----- Check if the option is supported + if (!isset($v_requested_options[$v_key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '".$v_key."' for this file"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for attribute + switch ($v_key) { + case PCLZIP_ATT_FILE_NAME : + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + + $p_filedescr['filename'] = PclZipUtilPathReduction($v_value); + + if ($p_filedescr['filename'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + + break; + + case PCLZIP_ATT_FILE_NEW_SHORT_NAME : + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + + $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value); + + if ($p_filedescr['new_short_name'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + break; + + case PCLZIP_ATT_FILE_NEW_FULL_NAME : + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + + $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value); + + if ($p_filedescr['new_full_name'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + break; + + // ----- Look for options that takes a string + case PCLZIP_ATT_FILE_COMMENT : + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + + $p_filedescr['comment'] = $v_value; + break; + + case PCLZIP_ATT_FILE_MTIME : + if (!is_integer($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". Integer expected for attribute '".PclZipUtilOptionText($v_key)."'"); + return PclZip::errorCode(); + } + + $p_filedescr['mtime'] = $v_value; + break; + + case PCLZIP_ATT_FILE_CONTENT : + $p_filedescr['content'] = $v_value; + break; + + default : + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, + "Unknown parameter '".$v_key."'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for mandatory options + if ($v_requested_options !== false) { + for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + // ----- Look if present + if (!isset($p_file_list[$key])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")"); + return PclZip::errorCode(); + } + } + } + } + + // end foreach + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privFileDescrExpand() + // Description : + // This method look for each item of the list to see if its a file, a folder + // or a string to be added as file. For any other type of files (link, other) + // just ignore the item. + // Then prepare the information that will be stored for that file. + // When its a folder, expand the folder with all the files that are in that + // folder (recursively). + // Parameters : + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + function privFileDescrExpand(&$p_filedescr_list, &$p_options) + { + $v_result=1; + + // ----- Create a result list + $v_result_list = array(); + + // ----- Look each entry + for ($i=0; $iprivCalculateStoredFilename($v_descr, $p_options); + + // ----- Add the descriptor in result list + $v_result_list[sizeof($v_result_list)] = $v_descr; + + // ----- Look for folder + if ($v_descr['type'] == 'folder') { + // ----- List of items in folder + $v_dirlist_descr = array(); + $v_dirlist_nb = 0; + if ($v_folder_handler = @opendir($v_descr['filename'])) { + while (($v_item_handler = @readdir($v_folder_handler)) !== false) { + + // ----- Skip '.' and '..' + if (($v_item_handler == '.') || ($v_item_handler == '..')) { + continue; + } + + // ----- Compose the full filename + $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'].'/'.$v_item_handler; + + // ----- Look for different stored filename + // Because the name of the folder was changed, the name of the + // files/sub-folders also change + if (($v_descr['stored_filename'] != $v_descr['filename']) + && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) { + if ($v_descr['stored_filename'] != '') { + $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'].'/'.$v_item_handler; + } + else { + $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler; + } + } + + $v_dirlist_nb++; + } + + @closedir($v_folder_handler); + } + else { + // TBC : unable to open folder in read mode + } + + // ----- Expand each element of the list + if ($v_dirlist_nb != 0) { + // ----- Expand + if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) { + return $v_result; + } + + // ----- Concat the resulting list + $v_result_list = array_merge($v_result_list, $v_dirlist_descr); + } + else { + } + + // ----- Free local array + unset($v_dirlist_descr); + } + } + + // ----- Get the result list + $p_filedescr_list = $v_result_list; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCreate() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privCreate($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result=1; + $v_list_detail = array(); + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the file in write mode + if (($v_result = $this->privOpenFd('wb')) != 1) + { + // ----- Return + return $v_result; + } + + // ----- Add the list of files + $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options); + + // ----- Close + $this->privCloseFd(); + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAdd() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privAdd($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result=1; + $v_list_detail = array(); + + // ----- Look if the archive exists or is empty + if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0)) + { + + // ----- Do a create + $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options); + + // ----- Return + return $v_result; + } + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the zip file + if (($v_result=$this->privOpenFd('rb')) != 1) + { + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + return $v_result; + } + + // ----- Go to beginning of File + @rewind($this->zip_fd); + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp'; + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) + { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = $v_central_dir['offset']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Swap the file descriptor + // Here is a trick : I swap the temporary fd with the zip fd, in order to use + // the following methods on the temporary fil and not the real archive + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Add the files + $v_header_list = array(); + if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) + { + fclose($v_zip_temp_fd); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($this->zip_fd); + + // ----- Copy the block of file headers from the old archive + $v_size = $v_central_dir['size']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_zip_temp_fd, $v_read_size); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Create the Central Dir files header + for ($i=0, $v_count=0; $iprivWriteCentralFileHeader($v_header_list[$i])) != 1) { + fclose($v_zip_temp_fd); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + $v_count++; + } + + // ----- Transform the header to a 'usable' info + $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + // ----- Zip file comment + $v_comment = $v_central_dir['comment']; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) { + $v_comment = $v_comment.$p_options[PCLZIP_OPT_ADD_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT].$v_comment; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($this->zip_fd)-$v_offset; + + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_count+$v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1) + { + // ----- Reset the file list + unset($v_header_list); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + + // ----- Swap back the file descriptor + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Close + $this->privCloseFd(); + + // ----- Close the temporary file + @fclose($v_zip_temp_fd); + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privOpenFd() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function privOpenFd($p_mode) + { + $v_result=1; + + // ----- Look if already open + if ($this->zip_fd != 0) + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \''.$this->zipname.'\' already open'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0) + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in '.$p_mode.' mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCloseFd() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function privCloseFd() + { + $v_result=1; + + if ($this->zip_fd != 0) + @fclose($this->zip_fd); + $this->zip_fd = 0; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddList() + // Description : + // $p_add_dir and $p_remove_dir will give the ability to memorize a path which is + // different from the real path of the file. This is usefull if you want to have PclTar + // running in any directory, and memorize relative path from an other directory. + // Parameters : + // $p_list : An array containing the file or directory names to add in the tar + // $p_result_list : list of added files with their properties (specially the status field) + // $p_add_dir : Path to add in the filename path archived + // $p_remove_dir : Path to remove in the filename path archived + // Return Values : + // -------------------------------------------------------------------------------- +// function privAddList($p_list, &$p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, &$p_options) + function privAddList($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result=1; + + // ----- Add the files + $v_header_list = array(); + if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) + { + // ----- Return + return $v_result; + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($this->zip_fd); + + // ----- Create the Central Dir files header + for ($i=0,$v_count=0; $iprivWriteCentralFileHeader($v_header_list[$i])) != 1) { + // ----- Return + return $v_result; + } + $v_count++; + } + + // ----- Transform the header to a 'usable' info + $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + // ----- Zip file comment + $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($this->zip_fd)-$v_offset; + + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1) + { + // ----- Reset the file list + unset($v_header_list); + + // ----- Return + return $v_result; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFileList() + // Description : + // Parameters : + // $p_filedescr_list : An array containing the file description + // or directory names to add in the zip + // $p_result_list : list of added files with their properties (specially the status field) + // Return Values : + // -------------------------------------------------------------------------------- + function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result=1; + $v_header = array(); + + // ----- Recuperate the current number of elt in list + $v_nb = sizeof($p_result_list); + + // ----- Loop on the files + for ($j=0; ($jprivAddFile($p_filedescr_list[$j], $v_header, + $p_options); + if ($v_result != 1) { + return $v_result; + } + + // ----- Store the file infos + $p_result_list[$v_nb++] = $v_header; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privAddFile($p_filedescr, &$p_header, &$p_options) + { + $v_result=1; + + // ----- Working variable + $p_filename = $p_filedescr['filename']; + + // TBC : Already done in the fileAtt check ... ? + if ($p_filename == "") { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for a stored different filename + /* TBC : Removed + if (isset($p_filedescr['stored_filename'])) { + $v_stored_filename = $p_filedescr['stored_filename']; + } + else { + $v_stored_filename = $p_filedescr['stored_filename']; + } + */ + + // ----- Set the file properties + clearstatcache(); + $p_header['version'] = 20; + $p_header['version_extracted'] = 10; + $p_header['flag'] = 0; + $p_header['compression'] = 0; + $p_header['crc'] = 0; + $p_header['compressed_size'] = 0; + $p_header['filename_len'] = strlen($p_filename); + $p_header['extra_len'] = 0; + $p_header['disk'] = 0; + $p_header['internal'] = 0; + $p_header['offset'] = 0; + $p_header['filename'] = $p_filename; +// TBC : Removed $p_header['stored_filename'] = $v_stored_filename; + $p_header['stored_filename'] = $p_filedescr['stored_filename']; + $p_header['extra'] = ''; + $p_header['status'] = 'ok'; + $p_header['index'] = -1; + + // ----- Look for regular file + if ($p_filedescr['type']=='file') { + $p_header['external'] = 0x00000000; + $p_header['size'] = filesize($p_filename); + } + + // ----- Look for regular folder + else if ($p_filedescr['type']=='folder') { + $p_header['external'] = 0x00000010; + $p_header['mtime'] = filemtime($p_filename); + $p_header['size'] = filesize($p_filename); + } + + // ----- Look for virtual file + else if ($p_filedescr['type'] == 'virtual_file') { + $p_header['external'] = 0x00000000; + $p_header['size'] = strlen($p_filedescr['content']); + } + + + // ----- Look for filetime + if (isset($p_filedescr['mtime'])) { + $p_header['mtime'] = $p_filedescr['mtime']; + } + else if ($p_filedescr['type'] == 'virtual_file') { + $p_header['mtime'] = time(); + } + else { + $p_header['mtime'] = filemtime($p_filename); + } + + // ------ Look for file comment + if (isset($p_filedescr['comment'])) { + $p_header['comment_len'] = strlen($p_filedescr['comment']); + $p_header['comment'] = $p_filedescr['comment']; + } + else { + $p_header['comment_len'] = 0; + $p_header['comment'] = ''; + } + + // ----- Look for pre-add callback + if (isset($p_options[PCLZIP_CB_PRE_ADD])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_header, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_ADD].'(PCLZIP_CB_PRE_ADD, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_header['status'] = "skipped"; + $v_result = 1; + } + + // ----- Update the informations + // Only some fields can be modified + if ($p_header['stored_filename'] != $v_local_header['stored_filename']) { + $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']); + } + } + + // ----- Look for empty stored filename + if ($p_header['stored_filename'] == "") { + $p_header['status'] = "filtered"; + } + + // ----- Check the path length + if (strlen($p_header['stored_filename']) > 0xFF) { + $p_header['status'] = 'filename_too_long'; + } + + // ----- Look if no error, or file not skipped + if ($p_header['status'] == 'ok') { + + // ----- Look for a file + if ($p_filedescr['type'] == 'file') { + // ----- Look for using temporary file to zip + if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) + && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) + || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) + && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])) ) ) { + $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options); + if ($v_result < PCLZIP_ERR_NO_ERROR) { + return $v_result; + } + } + + // ----- Use "in memory" zip algo + else { + + // ----- Open the source file + if (($v_file = @fopen($p_filename, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); + return PclZip::errorCode(); + } + + // ----- Read the file content + $v_content = @fread($v_file, $p_header['size']); + + // ----- Close the file + @fclose($v_file); + + // ----- Calculate the CRC + $p_header['crc'] = @crc32($v_content); + + // ----- Look for no compression + if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; + } + + // ----- Look for normal compression + else { + // ----- Compress the content + $v_content = @gzdeflate($v_content); + + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content); + $p_header['compression'] = 8; + } + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + @fclose($v_file); + return $v_result; + } + + // ----- Write the compressed (or not) content + @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + + } + + } + + // ----- Look for a virtual file (a file from string) + else if ($p_filedescr['type'] == 'virtual_file') { + + $v_content = $p_filedescr['content']; + + // ----- Calculate the CRC + $p_header['crc'] = @crc32($v_content); + + // ----- Look for no compression + if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; + } + + // ----- Look for normal compression + else { + // ----- Compress the content + $v_content = @gzdeflate($v_content); + + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content); + $p_header['compression'] = 8; + } + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + @fclose($v_file); + return $v_result; + } + + // ----- Write the compressed (or not) content + @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + } + + // ----- Look for a directory + else if ($p_filedescr['type'] == 'folder') { + // ----- Look for directory last '/' + if (@substr($p_header['stored_filename'], -1) != '/') { + $p_header['stored_filename'] .= '/'; + } + + // ----- Set the file properties + $p_header['size'] = 0; + //$p_header['external'] = 0x41FF0010; // Value for a folder : to be checked + $p_header['external'] = 0x00000010; // Value for a folder : to be checked + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) + { + return $v_result; + } + } + } + + // ----- Look for post-add callback + if (isset($p_options[PCLZIP_CB_POST_ADD])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_header, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_POST_ADD].'(PCLZIP_CB_POST_ADD, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header); + if ($v_result == 0) { + // ----- Ignored + $v_result = 1; + } + + // ----- Update the informations + // Nothing can be modified + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFileUsingTempFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options) + { + $v_result=PCLZIP_ERR_NO_ERROR; + + // ----- Working variable + $p_filename = $p_filedescr['filename']; + + + // ----- Open the source file + if (($v_file = @fopen($p_filename, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); + return PclZip::errorCode(); + } + + // ----- Creates a compressed temporary file + $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz'; + if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) { + fclose($v_file); + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode'); + return PclZip::errorCode(); + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = filesize($p_filename); + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_file, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @gzputs($v_file_compressed, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close the file + @fclose($v_file); + @gzclose($v_file_compressed); + + // ----- Check the minimum file size + if (filesize($v_gzip_temp_name) < 18) { + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \''.$v_gzip_temp_name.'\' has invalid filesize - should be minimum 18 bytes'); + return PclZip::errorCode(); + } + + // ----- Extract the compressed attributes + if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode'); + return PclZip::errorCode(); + } + + // ----- Read the gzip file header + $v_binary_data = @fread($v_file_compressed, 10); + $v_data_header = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data); + + // ----- Check some parameters + $v_data_header['os'] = bin2hex($v_data_header['os']); + + // ----- Read the gzip file footer + @fseek($v_file_compressed, filesize($v_gzip_temp_name)-8); + $v_binary_data = @fread($v_file_compressed, 8); + $v_data_footer = unpack('Vcrc/Vcompressed_size', $v_binary_data); + + // ----- Set the attributes + $p_header['compression'] = ord($v_data_header['cm']); + //$p_header['mtime'] = $v_data_header['mtime']; + $p_header['crc'] = $v_data_footer['crc']; + $p_header['compressed_size'] = filesize($v_gzip_temp_name)-18; + + // ----- Close the file + @fclose($v_file_compressed); + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + return $v_result; + } + + // ----- Add the compressed data + if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) + { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode'); + return PclZip::errorCode(); + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + fseek($v_file_compressed, 10); + $v_size = $p_header['compressed_size']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_file_compressed, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close the file + @fclose($v_file_compressed); + + // ----- Unlink the temporary file + @unlink($v_gzip_temp_name); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCalculateStoredFilename() + // Description : + // Based on file descriptor properties and global options, this method + // calculate the filename that will be stored in the archive. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privCalculateStoredFilename(&$p_filedescr, &$p_options) + { + $v_result=1; + + // ----- Working variables + $p_filename = $p_filedescr['filename']; + if (isset($p_options[PCLZIP_OPT_ADD_PATH])) { + $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH]; + } + else { + $p_add_dir = ''; + } + if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) { + $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH]; + } + else { + $p_remove_dir = ''; + } + if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + else { + $p_remove_all_dir = 0; + } + + + // ----- Look for full name change + if (isset($p_filedescr['new_full_name'])) { + // ----- Remove drive letter if any + $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']); + } + + // ----- Look for path and/or short name change + else { + + // ----- Look for short name change + // Its when we cahnge just the filename but not the path + if (isset($p_filedescr['new_short_name'])) { + $v_path_info = pathinfo($p_filename); + $v_dir = ''; + if ($v_path_info['dirname'] != '') { + $v_dir = $v_path_info['dirname'].'/'; + } + $v_stored_filename = $v_dir.$p_filedescr['new_short_name']; + } + else { + // ----- Calculate the stored filename + $v_stored_filename = $p_filename; + } + + // ----- Look for all path to remove + if ($p_remove_all_dir) { + $v_stored_filename = basename($p_filename); + } + // ----- Look for partial path remove + else if ($p_remove_dir != "") { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= "/"; + + if ( (substr($p_filename, 0, 2) == "./") + || (substr($p_remove_dir, 0, 2) == "./")) { + + if ( (substr($p_filename, 0, 2) == "./") + && (substr($p_remove_dir, 0, 2) != "./")) { + $p_remove_dir = "./".$p_remove_dir; + } + if ( (substr($p_filename, 0, 2) != "./") + && (substr($p_remove_dir, 0, 2) == "./")) { + $p_remove_dir = substr($p_remove_dir, 2); + } + } + + $v_compare = PclZipUtilPathInclusion($p_remove_dir, + $v_stored_filename); + if ($v_compare > 0) { + if ($v_compare == 2) { + $v_stored_filename = ""; + } + else { + $v_stored_filename = substr($v_stored_filename, + strlen($p_remove_dir)); + } + } + } + + // ----- Remove drive letter if any + $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename); + + // ----- Look for path to add + if ($p_add_dir != "") { + if (substr($p_add_dir, -1) == "/") + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir."/".$v_stored_filename; + } + } + + // ----- Filename (reduce the path of stored name) + $v_stored_filename = PclZipUtilPathReduction($v_stored_filename); + $p_filedescr['stored_filename'] = $v_stored_filename; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privWriteFileHeader(&$p_header) + { + $v_result=1; + + // ----- Store the offset position of the file + $p_header['offset'] = ftell($this->zip_fd); + + // ----- Transform UNIX mtime to DOS format mdate/mtime + $v_date = getdate($p_header['mtime']); + $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2; + $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday']; + + // ----- Packed data + $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50, + $p_header['version_extracted'], $p_header['flag'], + $p_header['compression'], $v_mtime, $v_mdate, + $p_header['crc'], $p_header['compressed_size'], + $p_header['size'], + strlen($p_header['stored_filename']), + $p_header['extra_len']); + + // ----- Write the first 148 bytes of the header in the archive + fputs($this->zip_fd, $v_binary_data, 30); + + // ----- Write the variable fields + if (strlen($p_header['stored_filename']) != 0) + { + fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); + } + if ($p_header['extra_len'] != 0) + { + fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteCentralFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privWriteCentralFileHeader(&$p_header) + { + $v_result=1; + + // TBC + //for(reset($p_header); $key = key($p_header); next($p_header)) { + //} + + // ----- Transform UNIX mtime to DOS format mdate/mtime + $v_date = getdate($p_header['mtime']); + $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2; + $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday']; + + + // ----- Packed data + $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, + $p_header['version'], $p_header['version_extracted'], + $p_header['flag'], $p_header['compression'], + $v_mtime, $v_mdate, $p_header['crc'], + $p_header['compressed_size'], $p_header['size'], + strlen($p_header['stored_filename']), + $p_header['extra_len'], $p_header['comment_len'], + $p_header['disk'], $p_header['internal'], + $p_header['external'], $p_header['offset']); + + // ----- Write the 42 bytes of the header in the zip file + fputs($this->zip_fd, $v_binary_data, 46); + + // ----- Write the variable fields + if (strlen($p_header['stored_filename']) != 0) + { + fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); + } + if ($p_header['extra_len'] != 0) + { + fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); + } + if ($p_header['comment_len'] != 0) + { + fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteCentralHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment) + { + $v_result=1; + + // ----- Packed data + $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, + $p_nb_entries, $p_size, + $p_offset, strlen($p_comment)); + + // ----- Write the 22 bytes of the header in the zip file + fputs($this->zip_fd, $v_binary_data, 22); + + // ----- Write the variable fields + if (strlen($p_comment) != 0) + { + fputs($this->zip_fd, $p_comment, strlen($p_comment)); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privList() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privList(&$p_list) + { + $v_result=1; + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) + { + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + { + $this->privSwapBackMagicQuotes(); + return $v_result; + } + + // ----- Go to beginning of Central Dir + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_central_dir['offset'])) + { + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read each entry + for ($i=0; $i<$v_central_dir['entries']; $i++) + { + // ----- Read the file header + if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) + { + $this->privSwapBackMagicQuotes(); + return $v_result; + } + $v_header['index'] = $i; + + // ----- Get the only interesting attributes + $this->privConvertHeader2FileInfo($v_header, $p_list[$i]); + unset($v_header); + } + + // ----- Close the zip file + $this->privCloseFd(); + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privConvertHeader2FileInfo() + // Description : + // This function takes the file informations from the central directory + // entries and extract the interesting parameters that will be given back. + // The resulting file infos are set in the array $p_info + // $p_info['filename'] : Filename with full path. Given by user (add), + // extracted in the filesystem (extract). + // $p_info['stored_filename'] : Stored filename in the archive. + // $p_info['size'] = Size of the file. + // $p_info['compressed_size'] = Compressed size of the file. + // $p_info['mtime'] = Last modification date of the file. + // $p_info['comment'] = Comment associated with the file. + // $p_info['folder'] = true/false : indicates if the entry is a folder or not. + // $p_info['status'] = status of the action on the file. + // $p_info['crc'] = CRC of the file content. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privConvertHeader2FileInfo($p_header, &$p_info) + { + $v_result=1; + + // ----- Get the interesting attributes + $v_temp_path = PclZipUtilPathReduction($p_header['filename']); + $p_info['filename'] = $v_temp_path; + $v_temp_path = PclZipUtilPathReduction($p_header['stored_filename']); + $p_info['stored_filename'] = $v_temp_path; + $p_info['size'] = $p_header['size']; + $p_info['compressed_size'] = $p_header['compressed_size']; + $p_info['mtime'] = $p_header['mtime']; + $p_info['comment'] = $p_header['comment']; + $p_info['folder'] = (($p_header['external']&0x00000010)==0x00000010); + $p_info['index'] = $p_header['index']; + $p_info['status'] = $p_header['status']; + $p_info['crc'] = $p_header['crc']; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractByRule() + // Description : + // Extract a file or directory depending of rules (by index, by name, ...) + // Parameters : + // $p_file_list : An array where will be placed the properties of each + // extracted file + // $p_path : Path to add while writing the extracted files + // $p_remove_path : Path to remove (from the file memorized path) while writing the + // extracted files. If the path does not match the file path, + // the file is extracted with its memorized path. + // $p_remove_path does not apply to 'list' mode. + // $p_path and $p_remove_path are commulative. + // Return Values : + // 1 on success,0 or less on error (see error code list) + // -------------------------------------------------------------------------------- + function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) + { + $v_result=1; + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Check the path + if ( ($p_path == "") + || ( (substr($p_path, 0, 1) != "/") + && (substr($p_path, 0, 3) != "../") + && (substr($p_path,1,2)!=":/"))) + $p_path = "./".$p_path; + + // ----- Reduce the path last (and duplicated) '/' + if (($p_path != "./") && ($p_path != "/")) + { + // ----- Look for the path end '/' + while (substr($p_path, -1) == "/") + { + $p_path = substr($p_path, 0, strlen($p_path)-1); + } + } + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/')) + { + $p_remove_path .= '/'; + } + $p_remove_path_size = strlen($p_remove_path); + + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) + { + $this->privSwapBackMagicQuotes(); + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; + + // ----- Read each entry + $j_start = 0; + for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++) + { + + // ----- Read next Central dir entry + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_pos_entry)) + { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the file header + $v_header = array(); + if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) + { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Store the index + $v_header['index'] = $i; + + // ----- Store the file position + $v_pos_entry = ftell($this->zip_fd); + + // ----- Look for the specific extract rules + $v_extract = false; + + // ----- Look for extract by name rule + if ( (isset($p_options[PCLZIP_OPT_BY_NAME])) + && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { + + // ----- Look if the filename is in the list + for ($j=0; ($j strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) + && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_extract = true; + } + } + // ----- Look for a filename + elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { + $v_extract = true; + } + } + } + + // ----- Look for extract by ereg rule + // ereg() is deprecated with PHP 5.3 + /* + else if ( (isset($p_options[PCLZIP_OPT_BY_EREG])) + && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { + + if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header['stored_filename'])) { + $v_extract = true; + } + } + */ + + // ----- Look for extract by preg rule + else if ( (isset($p_options[PCLZIP_OPT_BY_PREG])) + && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { + + if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) { + $v_extract = true; + } + } + + // ----- Look for extract by index rule + else if ( (isset($p_options[PCLZIP_OPT_BY_INDEX])) + && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { + + // ----- Look if the index is in the list + for ($j=$j_start; ($j=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { + $v_extract = true; + } + if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { + $j_start = $j+1; + } + + if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) { + break; + } + } + } + + // ----- Look for no rule, which means extract all the archive + else { + $v_extract = true; + } + + // ----- Check compression method + if ( ($v_extract) + && ( ($v_header['compression'] != 8) + && ($v_header['compression'] != 0))) { + $v_header['status'] = 'unsupported_compression'; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + + $this->privSwapBackMagicQuotes(); + + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION, + "Filename '".$v_header['stored_filename']."' is " + ."compressed by an unsupported compression " + ."method (".$v_header['compression'].") "); + + return PclZip::errorCode(); + } + } + + // ----- Check encrypted files + if (($v_extract) && (($v_header['flag'] & 1) == 1)) { + $v_header['status'] = 'unsupported_encryption'; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + + $this->privSwapBackMagicQuotes(); + + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, + "Unsupported encryption for " + ." filename '".$v_header['stored_filename'] + ."'"); + + return PclZip::errorCode(); + } + } + + // ----- Look for real extraction + if (($v_extract) && ($v_header['status'] != 'ok')) { + $v_result = $this->privConvertHeader2FileInfo($v_header, + $p_file_list[$v_nb_extracted++]); + if ($v_result != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + return $v_result; + } + + $v_extract = false; + } + + // ----- Look for real extraction + if ($v_extract) + { + + // ----- Go to the file position + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_header['offset'])) + { + // ----- Close the zip file + $this->privCloseFd(); + + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for extraction as string + if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) { + + $v_string = ''; + + // ----- Extracting the file + $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1) + { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Set the file content + $p_file_list[$v_nb_extracted]['content'] = $v_string; + + // ----- Next extracted file + $v_nb_extracted++; + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + } + // ----- Look for extraction in standard output + elseif ( (isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) + && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) { + // ----- Extracting the file in standard output + $v_result1 = $this->privExtractFileInOutput($v_header, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + return $v_result; + } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + } + // ----- Look for normal extraction + else { + // ----- Extracting the file + $v_result1 = $this->privExtractFile($v_header, + $p_path, $p_remove_path, + $p_remove_all_path, + $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) + { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + } + } + } + + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFile() + // Description : + // Parameters : + // Return Values : + // + // 1 : ... ? + // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback + // -------------------------------------------------------------------------------- + function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) + { + $v_result=1; + + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) + { + // ----- Return + return $v_result; + } + + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for all path to remove + if ($p_remove_all_path == true) { + // ----- Look for folder entry that not need to be extracted + if (($p_entry['external']&0x00000010)==0x00000010) { + + $p_entry['status'] = "filtered"; + + return $v_result; + } + + // ----- Get the basename of the path + $p_entry['filename'] = basename($p_entry['filename']); + } + + // ----- Look for path to remove + else if ($p_remove_path != "") + { + if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2) + { + + // ----- Change the file status + $p_entry['status'] = "filtered"; + + // ----- Return + return $v_result; + } + + $p_remove_path_size = strlen($p_remove_path); + if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path) + { + + // ----- Remove the path + $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size); + + } + } + + // ----- Add the path + if ($p_path != '') { + $p_entry['filename'] = $p_path."/".$p_entry['filename']; + } + + // ----- Check a base_dir_restriction + if (isset($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) { + $v_inclusion + = PclZipUtilPathInclusion($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION], + $p_entry['filename']); + if ($v_inclusion == 0) { + + PclZip::privErrorLog(PCLZIP_ERR_DIRECTORY_RESTRICTION, + "Filename '".$p_entry['filename']."' is " + ."outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION"); + + return PclZip::errorCode(); + } + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Look for specific actions while the file exist + if (file_exists($p_entry['filename'])) + { + + // ----- Look if file is a directory + if (is_dir($p_entry['filename'])) + { + + // ----- Change the file status + $p_entry['status'] = "already_a_directory"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + + PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY, + "Filename '".$p_entry['filename']."' is " + ."already used by an existing directory"); + + return PclZip::errorCode(); + } + } + // ----- Look if file is write protected + else if (!is_writeable($p_entry['filename'])) + { + + // ----- Change the file status + $p_entry['status'] = "write_protected"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, + "Filename '".$p_entry['filename']."' exists " + ."and is write protected"); + + return PclZip::errorCode(); + } + } + + // ----- Look if the extracted file is older + else if (filemtime($p_entry['filename']) > $p_entry['mtime']) + { + // ----- Change the file status + if ( (isset($p_options[PCLZIP_OPT_REPLACE_NEWER])) + && ($p_options[PCLZIP_OPT_REPLACE_NEWER]===true)) { + } + else { + $p_entry['status'] = "newer_exist"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ( (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) + && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) { + + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, + "Newer version of '".$p_entry['filename']."' exists " + ."and option PCLZIP_OPT_REPLACE_NEWER is not selected"); + + return PclZip::errorCode(); + } + } + } + else { + } + } + + // ----- Check the directory availability and create it if necessary + else { + if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/')) + $v_dir_to_check = $p_entry['filename']; + else if (!strstr($p_entry['filename'], "/")) + $v_dir_to_check = ""; + else + $v_dir_to_check = dirname($p_entry['filename']); + + if (($v_result = $this->privDirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) { + + // ----- Change the file status + $p_entry['status'] = "path_creation_fail"; + + // ----- Return + //return $v_result; + $v_result = 1; + } + } + } + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external']&0x00000010)==0x00000010)) + { + // ----- Look for not compressed file + if ($p_entry['compression'] == 0) { + + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) + { + + // ----- Change the file status + $p_entry['status'] = "write_error"; + + // ----- Return + return $v_result; + } + + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + /* Try to speed up the code + $v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_binary_data, $v_read_size); + */ + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Closing the destination file + fclose($v_dest_file); + + // ----- Change the file mtime + touch($p_entry['filename'], $p_entry['mtime']); + + + } + else { + // ----- TBC + // Need to be finished + if (($p_entry['flag'] & 1) == 1) { + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'File \''.$p_entry['filename'].'\' is encrypted. Encrypted files are not supported.'); + return PclZip::errorCode(); + } + + + // ----- Look for using temporary file to unzip + if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) + && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) + || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) + && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])) ) ) { + $v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options); + if ($v_result < PCLZIP_ERR_NO_ERROR) { + return $v_result; + } + } + + // ----- Look for extract in memory + else { + + + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + $v_file_content = @gzinflate($v_buffer); + unset($v_buffer); + if ($v_file_content === FALSE) { + + // ----- Change the file status + // TBC + $p_entry['status'] = "error"; + + return $v_result; + } + + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + + // ----- Change the file status + $p_entry['status'] = "write_error"; + + return $v_result; + } + + // ----- Write the uncompressed data + @fwrite($v_dest_file, $v_file_content, $p_entry['size']); + unset($v_file_content); + + // ----- Closing the destination file + @fclose($v_dest_file); + + } + + // ----- Change the file mtime + @touch($p_entry['filename'], $p_entry['mtime']); + } + + // ----- Look for chmod option + if (isset($p_options[PCLZIP_OPT_SET_CHMOD])) { + + // ----- Change the mode of the file + @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]); + } + + } + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + } + + // ----- Look for post-extract callback + elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileUsingTempFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privExtractFileUsingTempFile(&$p_entry, &$p_options) + { + $v_result=1; + + // ----- Creates a temporary file + $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz'; + if (($v_dest_file = @fopen($v_gzip_temp_name, "wb")) == 0) { + fclose($v_file); + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode'); + return PclZip::errorCode(); + } + + + // ----- Write gz file format header + $v_binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($p_entry['compression']), Chr(0x00), time(), Chr(0x00), Chr(3)); + @fwrite($v_dest_file, $v_binary_data, 10); + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Write gz file format footer + $v_binary_data = pack('VV', $p_entry['crc'], $p_entry['size']); + @fwrite($v_dest_file, $v_binary_data, 8); + + // ----- Close the temporary file + @fclose($v_dest_file); + + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + $p_entry['status'] = "write_error"; + return $v_result; + } + + // ----- Open the temporary gz file + if (($v_src_file = @gzopen($v_gzip_temp_name, 'rb')) == 0) { + @fclose($v_dest_file); + $p_entry['status'] = "read_error"; + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode'); + return PclZip::errorCode(); + } + + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($v_src_file, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + @fclose($v_dest_file); + @gzclose($v_src_file); + + // ----- Delete the temporary file + @unlink($v_gzip_temp_name); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileInOutput() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privExtractFileInOutput(&$p_entry, &$p_options) + { + $v_result=1; + + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + return $v_result; + } + + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + // ----- Trace + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external']&0x00000010)==0x00000010)) { + // ----- Look for not compressed file + if ($p_entry['compressed_size'] == $p_entry['size']) { + + // ----- Read the file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Send the file to the output + echo $v_buffer; + unset($v_buffer); + } + else { + + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + $v_file_content = gzinflate($v_buffer); + unset($v_buffer); + + // ----- Send the file to the output + echo $v_file_content; + unset($v_file_content); + } + } + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + } + + // ----- Look for post-extract callback + elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileAsString() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privExtractFileAsString(&$p_entry, &$p_string, &$p_options) + { + $v_result=1; + + // ----- Read the file header + $v_header = array(); + if (($v_result = $this->privReadFileHeader($v_header)) != 1) + { + // ----- Return + return $v_result; + } + + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external']&0x00000010)==0x00000010)) { + // ----- Look for not compressed file + // if ($p_entry['compressed_size'] == $p_entry['size']) + if ($p_entry['compression'] == 0) { + + // ----- Reading the file + $p_string = @fread($this->zip_fd, $p_entry['compressed_size']); + } + else { + + // ----- Reading the file + $v_data = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + if (($p_string = @gzinflate($v_data)) === FALSE) { + // TBC + } + } + + // ----- Trace + } + else { + // TBC : error : can not extract a folder in a string + } + + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + } + + // ----- Look for post-extract callback + elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Swap the content to header + $v_local_header['content'] = $p_string; + $p_string = ''; + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. +// eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + + // ----- Swap back the content to header + $p_string = $v_local_header['content']; + unset($v_local_header['content']); + + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privReadFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privReadFileHeader(&$p_header) + { + $v_result=1; + + // ----- Read the 4 bytes signature + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // ----- Check signature + if ($v_data['id'] != 0x04034b50) + { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the first 42 bytes of the header + $v_binary_data = fread($this->zip_fd, 26); + + // ----- Look for invalid block size + if (strlen($v_binary_data) != 26) + { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data)); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Extract the values + $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data); + + // ----- Get filename + $p_header['filename'] = fread($this->zip_fd, $v_data['filename_len']); + + // ----- Get extra_fields + if ($v_data['extra_len'] != 0) { + $p_header['extra'] = fread($this->zip_fd, $v_data['extra_len']); + } + else { + $p_header['extra'] = ''; + } + + // ----- Extract properties + $p_header['version_extracted'] = $v_data['version']; + $p_header['compression'] = $v_data['compression']; + $p_header['size'] = $v_data['size']; + $p_header['compressed_size'] = $v_data['compressed_size']; + $p_header['crc'] = $v_data['crc']; + $p_header['flag'] = $v_data['flag']; + $p_header['filename_len'] = $v_data['filename_len']; + + // ----- Recuperate date in UNIX format + $p_header['mdate'] = $v_data['mdate']; + $p_header['mtime'] = $v_data['mtime']; + if ($p_header['mdate'] && $p_header['mtime']) + { + // ----- Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F)*2; + + // ----- Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // ----- Get UNIX date format + $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } + else + { + $p_header['mtime'] = time(); + } + + // TBC + //for(reset($v_data); $key = key($v_data); next($v_data)) { + //} + + // ----- Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; + + // ----- Set the status field + $p_header['status'] = "ok"; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privReadCentralFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privReadCentralFileHeader(&$p_header) + { + $v_result=1; + + // ----- Read the 4 bytes signature + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // ----- Check signature + if ($v_data['id'] != 0x02014b50) + { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the first 42 bytes of the header + $v_binary_data = fread($this->zip_fd, 42); + + // ----- Look for invalid block size + if (strlen($v_binary_data) != 42) + { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data)); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Extract the values + $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data); + + // ----- Get filename + if ($p_header['filename_len'] != 0) + $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']); + else + $p_header['filename'] = ''; + + // ----- Get extra + if ($p_header['extra_len'] != 0) + $p_header['extra'] = fread($this->zip_fd, $p_header['extra_len']); + else + $p_header['extra'] = ''; + + // ----- Get comment + if ($p_header['comment_len'] != 0) + $p_header['comment'] = fread($this->zip_fd, $p_header['comment_len']); + else + $p_header['comment'] = ''; + + // ----- Extract properties + + // ----- Recuperate date in UNIX format + //if ($p_header['mdate'] && $p_header['mtime']) + // TBC : bug : this was ignoring time with 0/0/0 + if (1) + { + // ----- Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F)*2; + + // ----- Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // ----- Get UNIX date format + $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } + else + { + $p_header['mtime'] = time(); + } + + // ----- Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; + + // ----- Set default status to ok + $p_header['status'] = 'ok'; + + // ----- Look if it is a directory + if (substr($p_header['filename'], -1) == '/') { + //$p_header['external'] = 0x41FF0010; + $p_header['external'] = 0x00000010; + } + + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCheckFileHeaders() + // Description : + // Parameters : + // Return Values : + // 1 on success, + // 0 on error; + // -------------------------------------------------------------------------------- + function privCheckFileHeaders(&$p_local_header, &$p_central_header) + { + $v_result=1; + + // ----- Check the static values + // TBC + if ($p_local_header['filename'] != $p_central_header['filename']) { + } + if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) { + } + if ($p_local_header['flag'] != $p_central_header['flag']) { + } + if ($p_local_header['compression'] != $p_central_header['compression']) { + } + if ($p_local_header['mtime'] != $p_central_header['mtime']) { + } + if ($p_local_header['filename_len'] != $p_central_header['filename_len']) { + } + + // ----- Look for flag bit 3 + if (($p_local_header['flag'] & 8) == 8) { + $p_local_header['size'] = $p_central_header['size']; + $p_local_header['compressed_size'] = $p_central_header['compressed_size']; + $p_local_header['crc'] = $p_central_header['crc']; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privReadEndCentralDir() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privReadEndCentralDir(&$p_central_dir) + { + $v_result=1; + + // ----- Go to the end of the zip file + $v_size = filesize($this->zipname); + @fseek($this->zip_fd, $v_size); + if (@ftell($this->zip_fd) != $v_size) + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to go to the end of the archive \''.$this->zipname.'\''); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- First try : look if this is an archive with no commentaries (most of the time) + // in this case the end of central dir is at 22 bytes of the file end + $v_found = 0; + if ($v_size > 26) { + @fseek($this->zip_fd, $v_size-22); + if (($v_pos = @ftell($this->zip_fd)) != ($v_size-22)) + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\''); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read for bytes + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = @unpack('Vid', $v_binary_data); + + // ----- Check signature + if ($v_data['id'] == 0x06054b50) { + $v_found = 1; + } + + $v_pos = ftell($this->zip_fd); + } + + // ----- Go back to the maximum possible size of the Central Dir End Record + if (!$v_found) { + $v_maximum_size = 65557; // 0xFFFF + 22; + if ($v_maximum_size > $v_size) + $v_maximum_size = $v_size; + @fseek($this->zip_fd, $v_size-$v_maximum_size); + if (@ftell($this->zip_fd) != ($v_size-$v_maximum_size)) + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\''); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read byte per byte in order to find the signature + $v_pos = ftell($this->zip_fd); + $v_bytes = 0x00000000; + while ($v_pos < $v_size) + { + // ----- Read a byte + $v_byte = @fread($this->zip_fd, 1); + + // ----- Add the byte + //$v_bytes = ($v_bytes << 8) | Ord($v_byte); + // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number + // Otherwise on systems where we have 64bit integers the check below for the magic number will fail. + $v_bytes = ( ($v_bytes & 0xFFFFFF) << 8) | Ord($v_byte); + + // ----- Compare the bytes + if ($v_bytes == 0x504b0506) + { + $v_pos++; + break; + } + + $v_pos++; + } + + // ----- Look if not found end of central dir + if ($v_pos == $v_size) + { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Unable to find End of Central Dir Record signature"); + + // ----- Return + return PclZip::errorCode(); + } + } + + // ----- Read the first 18 bytes of the header + $v_binary_data = fread($this->zip_fd, 18); + + // ----- Look for invalid block size + if (strlen($v_binary_data) != 18) + { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid End of Central Dir Record size : ".strlen($v_binary_data)); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Extract the values + $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data); + + // ----- Check the global size + if (($v_pos + $v_data['comment_size'] + 18) != $v_size) { + + // ----- Removed in release 2.2 see readme file + // The check of the file size is a little too strict. + // Some bugs where found when a zip is encrypted/decrypted with 'crypt'. + // While decrypted, zip has training 0 bytes + if (0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, + 'The central dir is not at the end of the archive.' + .' Some trailing bytes exists after the archive.'); + + // ----- Return + return PclZip::errorCode(); + } + } + + // ----- Get comment + if ($v_data['comment_size'] != 0) { + $p_central_dir['comment'] = fread($this->zip_fd, $v_data['comment_size']); + } + else + $p_central_dir['comment'] = ''; + + $p_central_dir['entries'] = $v_data['entries']; + $p_central_dir['disk_entries'] = $v_data['disk_entries']; + $p_central_dir['offset'] = $v_data['offset']; + $p_central_dir['size'] = $v_data['size']; + $p_central_dir['disk'] = $v_data['disk']; + $p_central_dir['disk_start'] = $v_data['disk_start']; + + // TBC + //for(reset($p_central_dir); $key = key($p_central_dir); next($p_central_dir)) { + //} + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDeleteByRule() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privDeleteByRule(&$p_result_list, &$p_options) + { + $v_result=1; + $v_list_detail = array(); + + // ----- Open the zip file + if (($v_result=$this->privOpenFd('rb')) != 1) + { + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + { + $this->privCloseFd(); + return $v_result; + } + + // ----- Go to beginning of File + @rewind($this->zip_fd); + + // ----- Scan all the files + // ----- Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_pos_entry)) + { + // ----- Close the zip file + $this->privCloseFd(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read each entry + $v_header_list = array(); + $j_start = 0; + for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++) + { + + // ----- Read the file header + $v_header_list[$v_nb_extracted] = array(); + if (($v_result = $this->privReadCentralFileHeader($v_header_list[$v_nb_extracted])) != 1) + { + // ----- Close the zip file + $this->privCloseFd(); + + return $v_result; + } + + + // ----- Store the index + $v_header_list[$v_nb_extracted]['index'] = $i; + + // ----- Look for the specific extract rules + $v_found = false; + + // ----- Look for extract by name rule + if ( (isset($p_options[PCLZIP_OPT_BY_NAME])) + && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { + + // ----- Look if the filename is in the list + for ($j=0; ($j strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) + && (substr($v_header_list[$v_nb_extracted]['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_found = true; + } + elseif ( (($v_header_list[$v_nb_extracted]['external']&0x00000010)==0x00000010) /* Indicates a folder */ + && ($v_header_list[$v_nb_extracted]['stored_filename'].'/' == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_found = true; + } + } + // ----- Look for a filename + elseif ($v_header_list[$v_nb_extracted]['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { + $v_found = true; + } + } + } + + // ----- Look for extract by ereg rule + // ereg() is deprecated with PHP 5.3 + /* + else if ( (isset($p_options[PCLZIP_OPT_BY_EREG])) + && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { + + if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { + $v_found = true; + } + } + */ + + // ----- Look for extract by preg rule + else if ( (isset($p_options[PCLZIP_OPT_BY_PREG])) + && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { + + if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { + $v_found = true; + } + } + + // ----- Look for extract by index rule + else if ( (isset($p_options[PCLZIP_OPT_BY_INDEX])) + && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { + + // ----- Look if the index is in the list + for ($j=$j_start; ($j=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { + $v_found = true; + } + if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { + $j_start = $j+1; + } + + if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) { + break; + } + } + } + else { + $v_found = true; + } + + // ----- Look for deletion + if ($v_found) + { + unset($v_header_list[$v_nb_extracted]); + } + else + { + $v_nb_extracted++; + } + } + + // ----- Look if something need to be deleted + if ($v_nb_extracted > 0) { + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp'; + + // ----- Creates a temporary zip archive + $v_temp_zip = new PclZip($v_zip_temp_name); + + // ----- Open the temporary zip file in write mode + if (($v_result = $v_temp_zip->privOpenFd('wb')) != 1) { + $this->privCloseFd(); + + // ----- Return + return $v_result; + } + + // ----- Look which file need to be kept + for ($i=0; $izip_fd); + if (@fseek($this->zip_fd, $v_header_list[$i]['offset'])) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the file header + $v_local_header = array(); + if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Check that local file header is same as central file header + if ($this->privCheckFileHeaders($v_local_header, + $v_header_list[$i]) != 1) { + // TBC + } + unset($v_local_header); + + // ----- Write the file header + if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Read/write the data block + if (($v_result = PclZipUtilCopyBlock($this->zip_fd, $v_temp_zip->zip_fd, $v_header_list[$i]['compressed_size'])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($v_temp_zip->zip_fd); + + // ----- Re-Create the Central Dir files header + for ($i=0; $iprivWriteCentralFileHeader($v_header_list[$i])) != 1) { + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Transform the header to a 'usable' info + $v_temp_zip->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + + // ----- Zip file comment + $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($v_temp_zip->zip_fd)-$v_offset; + + // ----- Create the central dir footer + if (($v_result = $v_temp_zip->privWriteCentralHeader(sizeof($v_header_list), $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Close + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Destroy the temporary archive + unset($v_temp_zip); + } + + // ----- Remove every files : reset the file + else if ($v_central_dir['entries'] != 0) { + $this->privCloseFd(); + + if (($v_result = $this->privOpenFd('wb')) != 1) { + return $v_result; + } + + if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) { + return $v_result; + } + + $this->privCloseFd(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDirCheck() + // Description : + // Check if a directory exists, if not it creates it and all the parents directory + // which may be useful. + // Parameters : + // $p_dir : Directory path to check. + // Return Values : + // 1 : OK + // -1 : Unable to create directory + // -------------------------------------------------------------------------------- + function privDirCheck($p_dir, $p_is_dir=false) + { + $v_result = 1; + + + // ----- Remove the final '/' + if (($p_is_dir) && (substr($p_dir, -1)=='/')) + { + $p_dir = substr($p_dir, 0, strlen($p_dir)-1); + } + + // ----- Check the directory availability + if ((is_dir($p_dir)) || ($p_dir == "")) + { + return 1; + } + + // ----- Extract parent directory + $p_parent_dir = dirname($p_dir); + + // ----- Just a check + if ($p_parent_dir != $p_dir) + { + // ----- Look for parent directory + if ($p_parent_dir != "") + { + if (($v_result = $this->privDirCheck($p_parent_dir)) != 1) + { + return $v_result; + } + } + } + + // ----- Create the directory + if (!@mkdir($p_dir, 0777)) + { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_DIR_CREATE_FAIL, "Unable to create directory '$p_dir'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privMerge() + // Description : + // If $p_archive_to_add does not exist, the function exit with a success result. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privMerge(&$p_archive_to_add) + { + $v_result=1; + + // ----- Look if the archive_to_add exists + if (!is_file($p_archive_to_add->zipname)) + { + + // ----- Nothing to merge, so merge is a success + $v_result = 1; + + // ----- Return + return $v_result; + } + + // ----- Look if the archive exists + if (!is_file($this->zipname)) + { + + // ----- Do a duplicate + $v_result = $this->privDuplicate($p_archive_to_add->zipname); + + // ----- Return + return $v_result; + } + + // ----- Open the zip file + if (($v_result=$this->privOpenFd('rb')) != 1) + { + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) + { + $this->privCloseFd(); + return $v_result; + } + + // ----- Go to beginning of File + @rewind($this->zip_fd); + + // ----- Open the archive_to_add file + if (($v_result=$p_archive_to_add->privOpenFd('rb')) != 1) + { + $this->privCloseFd(); + + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir_to_add = array(); + if (($v_result = $p_archive_to_add->privReadEndCentralDir($v_central_dir_to_add)) != 1) + { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + + return $v_result; + } + + // ----- Go to beginning of File + @rewind($p_archive_to_add->zip_fd); + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp'; + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) + { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = $v_central_dir['offset']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Copy the files from the archive_to_add into the temporary file + $v_size = $v_central_dir_to_add['offset']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($p_archive_to_add->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($v_zip_temp_fd); + + // ----- Copy the block of file headers from the old archive + $v_size = $v_central_dir['size']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Copy the block of file headers from the archive_to_add + $v_size = $v_central_dir_to_add['size']; + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_archive_to_add->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Merge the file comments + $v_comment = $v_central_dir['comment'].' '.$v_central_dir_to_add['comment']; + + // ----- Calculate the size of the (new) central header + $v_size = @ftell($v_zip_temp_fd)-$v_offset; + + // ----- Swap the file descriptor + // Here is a trick : I swap the temporary fd with the zip fd, in order to use + // the following methods on the temporary fil and not the real archive fd + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_central_dir['entries']+$v_central_dir_to_add['entries'], $v_size, $v_offset, $v_comment)) != 1) + { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + @fclose($v_zip_temp_fd); + $this->zip_fd = null; + + // ----- Reset the file list + unset($v_header_list); + + // ----- Return + return $v_result; + } + + // ----- Swap back the file descriptor + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Close + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + + // ----- Close the temporary file + @fclose($v_zip_temp_fd); + + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDuplicate() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privDuplicate($p_archive_filename) + { + $v_result=1; + + // ----- Look if the $p_archive_filename exists + if (!is_file($p_archive_filename)) + { + + // ----- Nothing to duplicate, so duplicate is a success. + $v_result = 1; + + // ----- Return + return $v_result; + } + + // ----- Open the zip file + if (($v_result=$this->privOpenFd('wb')) != 1) + { + // ----- Return + return $v_result; + } + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($p_archive_filename, 'rb')) == 0) + { + $this->privCloseFd(); + + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive file \''.$p_archive_filename.'\' in binary write mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = filesize($p_archive_filename); + while ($v_size != 0) + { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($v_zip_temp_fd, $v_read_size); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close + $this->privCloseFd(); + + // ----- Close the temporary file + @fclose($v_zip_temp_fd); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privErrorLog() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function privErrorLog($p_error_code=0, $p_error_string='') + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + PclError($p_error_code, $p_error_string); + } + else { + $this->error_code = $p_error_code; + $this->error_string = $p_error_string; + } + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privErrorReset() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + function privErrorReset() + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + PclErrorReset(); + } + else { + $this->error_code = 0; + $this->error_string = ''; + } + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDisableMagicQuotes() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privDisableMagicQuotes() + { + $v_result=1; + + // ----- Look if function exists + if ( (!function_exists("get_magic_quotes_runtime")) + || (!function_exists("set_magic_quotes_runtime"))) { + return $v_result; + } + + // ----- Look if already done + if ($this->magic_quotes_status != -1) { + return $v_result; + } + + // ----- Get and memorize the magic_quote value + $this->magic_quotes_status = @get_magic_quotes_runtime(); + + // ----- Disable magic_quotes + if ($this->magic_quotes_status == 1) { + @set_magic_quotes_runtime(0); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privSwapBackMagicQuotes() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function privSwapBackMagicQuotes() + { + $v_result=1; + + // ----- Look if function exists + if ( (!function_exists("get_magic_quotes_runtime")) + || (!function_exists("set_magic_quotes_runtime"))) { + return $v_result; + } + + // ----- Look if something to do + if ($this->magic_quotes_status != -1) { + return $v_result; + } + + // ----- Swap back magic_quotes + if ($this->magic_quotes_status == 1) { + @set_magic_quotes_runtime($this->magic_quotes_status); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + } + // End of class + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : PclZipUtilPathReduction() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + function PclZipUtilPathReduction($p_dir) + { + $v_result = ""; + + // ----- Look for not empty path + if ($p_dir != "") { + // ----- Explode path by directory names + $v_list = explode("/", $p_dir); + + // ----- Study directories from last to first + $v_skip = 0; + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + $v_skip++; + } + else if ($v_list[$i] == "") { + // ----- First '/' i.e. root slash + if ($i == 0) { + $v_result = "/".$v_result; + if ($v_skip > 0) { + // ----- It is an invalid path, so the path is not modified + // TBC + $v_result = $p_dir; + $v_skip = 0; + } + } + // ----- Last '/' i.e. indicates a directory + else if ($i == (sizeof($v_list)-1)) { + $v_result = $v_list[$i]; + } + // ----- Double '/' inside the path + else { + // ----- Ignore only the double '//' in path, + // but not the first and last '/' + } + } + else { + // ----- Look for item to skip + if ($v_skip > 0) { + $v_skip--; + } + else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:""); + } + } + } + + // ----- Look for skip + if ($v_skip > 0) { + while ($v_skip > 0) { + $v_result = '../'.$v_result; + $v_skip--; + } + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : PclZipUtilPathInclusion() + // Description : + // This function indicates if the path $p_path is under the $p_dir tree. Or, + // said in an other way, if the file or sub-dir $p_path is inside the dir + // $p_dir. + // The function indicates also if the path is exactly the same as the dir. + // This function supports path with duplicated '/' like '//', but does not + // support '.' or '..' statements. + // Parameters : + // Return Values : + // 0 if $p_path is not inside directory $p_dir + // 1 if $p_path is inside directory $p_dir + // 2 if $p_path is exactly the same as $p_dir + // -------------------------------------------------------------------------------- + function PclZipUtilPathInclusion($p_dir, $p_path) + { + $v_result = 1; + + // ----- Look for path beginning by ./ + if ( ($p_dir == '.') + || ((strlen($p_dir) >=2) && (substr($p_dir, 0, 2) == './'))) { + $p_dir = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_dir, 1); + } + if ( ($p_path == '.') + || ((strlen($p_path) >=2) && (substr($p_path, 0, 2) == './'))) { + $p_path = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_path, 1); + } + + // ----- Explode dir and path by directory separator + $v_list_dir = explode("/", $p_dir); + $v_list_dir_size = sizeof($v_list_dir); + $v_list_path = explode("/", $p_path); + $v_list_path_size = sizeof($v_list_path); + + // ----- Study directories paths + $i = 0; + $j = 0; + while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) { + + // ----- Look for empty dir (path reduction) + if ($v_list_dir[$i] == '') { + $i++; + continue; + } + if ($v_list_path[$j] == '') { + $j++; + continue; + } + + // ----- Compare the items + if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != '')) { + $v_result = 0; + } + + // ----- Next items + $i++; + $j++; + } + + // ----- Look if everything seems to be the same + if ($v_result) { + // ----- Skip all the empty items + while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++; + while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++; + + if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) { + // ----- There are exactly the same + $v_result = 2; + } + else if ($i < $v_list_dir_size) { + // ----- The path is shorter than the dir + $v_result = 0; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : PclZipUtilCopyBlock() + // Description : + // Parameters : + // $p_mode : read/write compression mode + // 0 : src & dest normal + // 1 : src gzip, dest normal + // 2 : src normal, dest gzip + // 3 : src & dest gzip + // Return Values : + // -------------------------------------------------------------------------------- + function PclZipUtilCopyBlock($p_src, $p_dest, $p_size, $p_mode=0) + { + $v_result = 1; + + if ($p_mode==0) + { + while ($p_size != 0) + { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + else if ($p_mode==1) + { + while ($p_size != 0) + { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + else if ($p_mode==2) + { + while ($p_size != 0) + { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + else if ($p_mode==3) + { + while ($p_size != 0) + { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : PclZipUtilRename() + // Description : + // This function tries to do a simple rename() function. If it fails, it + // tries to copy the $p_src file in a new $p_dest file and then unlink the + // first one. + // Parameters : + // $p_src : Old filename + // $p_dest : New filename + // Return Values : + // 1 on success, 0 on failure. + // -------------------------------------------------------------------------------- + function PclZipUtilRename($p_src, $p_dest) + { + $v_result = 1; + + // ----- Try to rename the files + if (!@rename($p_src, $p_dest)) { + + // ----- Try to copy & unlink the src + if (!@copy($p_src, $p_dest)) { + $v_result = 0; + } + else if (!@unlink($p_src)) { + $v_result = 0; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : PclZipUtilOptionText() + // Description : + // Translate option value in text. Mainly for debug purpose. + // Parameters : + // $p_option : the option value. + // Return Values : + // The option text value. + // -------------------------------------------------------------------------------- + function PclZipUtilOptionText($p_option) + { + + $v_list = get_defined_constants(); + for (reset($v_list); $v_key = key($v_list); next($v_list)) { + $v_prefix = substr($v_key, 0, 10); + if (( ($v_prefix == 'PCLZIP_OPT') + || ($v_prefix == 'PCLZIP_CB_') + || ($v_prefix == 'PCLZIP_ATT')) + && ($v_list[$v_key] == $p_option)) { + return $v_key; + } + } + + $v_result = 'Unknown'; + + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : PclZipUtilTranslateWinPath() + // Description : + // Translate windows path by replacing '\' by '/' and optionally removing + // drive letter. + // Parameters : + // $p_path : path to translate. + // $p_remove_disk_letter : true | false + // Return Values : + // The path translated. + // -------------------------------------------------------------------------------- + function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter=true) + { + if (stristr(php_uname(), 'windows')) { + // ----- Look for potential disk letter + if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // -------------------------------------------------------------------------------- + + +?> diff --git a/trunk/lib/pclzip/readme.txt b/trunk/lib/pclzip/readme.txt new file mode 100644 index 0000000000..6ed8839477 --- /dev/null +++ b/trunk/lib/pclzip/readme.txt @@ -0,0 +1,421 @@ +// -------------------------------------------------------------------------------- +// PclZip 2.8.2 - readme.txt +// -------------------------------------------------------------------------------- +// License GNU/LGPL - August 2009 +// Vincent Blavet - vincent@phpconcept.net +// http://www.phpconcept.net +// -------------------------------------------------------------------------------- +// $Id: readme.txt,v 1.60 2009/09/30 20:35:21 vblavet Exp $ +// -------------------------------------------------------------------------------- + + + +0 - Sommaire +============ + 1 - Introduction + 2 - What's new + 3 - Corrected bugs + 4 - Known bugs or limitations + 5 - License + 6 - Warning + 7 - Documentation + 8 - Author + 9 - Contribute + +1 - Introduction +================ + + PclZip is a library that allow you to manage a Zip archive. + + Full documentation about PclZip can be found here : http://www.phpconcept.net/pclzip + +2 - What's new +============== + + Version 2.8.2 : + - PCLZIP_CB_PRE_EXTRACT and PCLZIP_CB_POST_EXTRACT are now supported with + extraction as a string (PCLZIP_OPT_EXTRACT_AS_STRING). The string + can also be modified in the post-extract call back. + **Bugs correction : + - PCLZIP_OPT_REMOVE_ALL_PATH was not working correctly + - Remove use of eval() and do direct call to callback functions + - Correct support of 64bits systems (Thanks to WordPress team) + + Version 2.8.1 : + - Move option PCLZIP_OPT_BY_EREG to PCLZIP_OPT_BY_PREG because ereg() is + deprecated in PHP 5.3. When using option PCLZIP_OPT_BY_EREG, PclZip will + automatically replace it by PCLZIP_OPT_BY_PREG. + + Version 2.8 : + - Improve extraction of zip archive for large files by using temporary files + This feature is working like the one defined in r2.7. + Options are renamed : PCLZIP_OPT_TEMP_FILE_ON, PCLZIP_OPT_TEMP_FILE_OFF, + PCLZIP_OPT_TEMP_FILE_THRESHOLD + - Add a ratio constant PCLZIP_TEMPORARY_FILE_RATIO to configure the auto + sense of temporary file use. + - Bug correction : Reduce filepath in returned file list to remove ennoying + './/' preambule in file path. + + Version 2.7 : + - Improve creation of zip archive for large files : + PclZip will now autosense the configured memory and use temporary files + when large file is suspected. + This feature can also ne triggered by manual options in create() and add() + methods. 'PCLZIP_OPT_ADD_TEMP_FILE_ON' force the use of temporary files, + 'PCLZIP_OPT_ADD_TEMP_FILE_OFF' disable the autosense technic, + 'PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD' allow for configuration of a size + threshold to use temporary files. + Using "temporary files" rather than "memory" might take more time, but + might give the ability to zip very large files : + Tested on my win laptop with a 88Mo file : + Zip "in-memory" : 18sec (max_execution_time=30, memory_limit=180Mo) + Zip "tmporary-files" : 23sec (max_execution_time=30, memory_limit=30Mo) + - Replace use of mktime() by time() to limit the E_STRICT error messages. + - Bug correction : When adding files with full windows path (drive letter) + PclZip is now working. Before, if the drive letter is not the default + path, PclZip was not able to add the file. + + Version 2.6 : + - Code optimisation + - New attributes PCLZIP_ATT_FILE_COMMENT gives the ability to + add a comment for a specific file. (Don't really know if this is usefull) + - New attribute PCLZIP_ATT_FILE_CONTENT gives the ability to add a string + as a file. + - New attribute PCLZIP_ATT_FILE_MTIME modify the timestamp associated with + a file. + - Correct a bug. Files archived with a timestamp with 0h0m0s were extracted + with current time + - Add CRC value in the informations returned back for each file after an + action. + - Add missing closedir() statement. + - When adding a folder, and removing the path of this folder, files were + incorrectly added with a '/' at the beginning. Which means files are + related to root in unix systems. Corrected. + - Add conditional if before constant definition. This will allow users + to redefine constants without changing the file, and then improve + upgrade of pclzip code for new versions. + + Version 2.5 : + - Introduce the ability to add file/folder with individual properties (file descriptor). + This gives for example the ability to change the filename of a zipped file. + . Able to add files individually + . Able to change full name + . Able to change short name + . Compatible with global options + - New attributes : PCLZIP_ATT_FILE_NAME, PCLZIP_ATT_FILE_NEW_SHORT_NAME, PCLZIP_ATT_FILE_NEW_FULL_NAME + - New error code : PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE + - Add a security control feature. PclZip can extract any file in any folder + of a system. People may use this to upload a zip file and try to override + a system file. The PCLZIP_OPT_EXTRACT_DIR_RESTRICTION will give the + ability to forgive any directory transversal behavior. + - New PCLZIP_OPT_EXTRACT_DIR_RESTRICTION : check extraction path + - New error code : PCLZIP_ERR_DIRECTORY_RESTRICTION + - Modification in PclZipUtilPathInclusion() : dir and path beginning with ./ will be prepend + by current path (getcwd()) + + Version 2.4 : + - Code improvment : try to speed up the code by removing unusefull call to pack() + - Correct bug in delete() : delete() should be called with no argument. This was not + the case in 2.3. This is corrected in 2.4. + - Correct a bug in path_inclusion function. When the path has several '../../', the + result was bad. + - Add a check for magic_quotes_runtime configuration. If enabled, PclZip will + disable it while working and det it back to its original value. + This resolve a lots of bad formated archive errors. + - Bug correction : PclZip now correctly unzip file in some specific situation, + when compressed content has same size as uncompressed content. + - Bug correction : When selecting option 'PCLZIP_OPT_REMOVE_ALL_PATH', + directories are not any more created. + - Code improvment : correct unclosed opendir(), better handling of . and .. in + loops. + + + Version 2.3 : + - Correct a bug with PHP5 : affecting the value 0xFE49FFE0 to a variable does not + give the same result in PHP4 and PHP5 .... + + Version 2.2 : + - Try development of PCLZIP_OPT_CRYPT ..... + However this becomes to a stop. To crypt/decrypt I need to multiply 2 long integers, + the result (greater than a long) is not supported by PHP. Even the use of bcmath + functions does not help. I did not find yet a solution ...; + - Add missing '/' at end of directory entries + - Check is a file is encrypted or not. Returns status 'unsupported_encryption' and/or + error code PCLZIP_ERR_UNSUPPORTED_ENCRYPTION. + - Corrected : Bad "version need to extract" field in local file header + - Add private method privCheckFileHeaders() in order to check local and central + file headers. PclZip is now supporting purpose bit flag bit 3. Purpose bit flag bit 3 gives + the ability to have a local file header without size, compressed size and crc filled. + - Add a generic status 'error' for file status + - Add control of compression type. PclZip only support deflate compression method. + Before v2.2, PclZip does not check the compression method used in an archive while + extracting. With v2.2 PclZip returns a new error status for a file using an unsupported + compression method. New status is "unsupported_compression". New error code is + PCLZIP_ERR_UNSUPPORTED_COMPRESSION. + - Add optional attribute PCLZIP_OPT_STOP_ON_ERROR. This will stop the extract of files + when errors like 'a folder with same name exists' or 'a newer file exists' or + 'a write protected file' exists, rather than set a status for the concerning file + and resume the extract of the zip. + - Add optional attribute PCLZIP_OPT_REPLACE_NEWER. This will force, during an extract' the + replacement of the file, even if a newer version of the file exists. + Note that today if a file with the same name already exists but is older it will be + replaced by the extracted one. + - Improve PclZipUtilOption() + - Support of zip archive with trailing bytes. Before 2.2, PclZip checks that the central + directory structure is the last data in the archive. Crypt encryption/decryption of + zip archive put trailing 0 bytes after decryption. PclZip is now supporting this. + + Version 2.1 : + - Add the ability to abort the extraction by using a user callback function. + The user can now return the value '2' in its callback which indicates to stop the + extraction. For a pre call-back extract is stopped before the extration of the current + file. For a post call back, the extraction is stopped after. + - Add the ability to extract a file (or several files) directly in the standard output. + This is done by the new parameter PCLZIP_OPT_EXTRACT_IN_OUTPUT with method extract(). + - Add support for parameters PCLZIP_OPT_COMMENT, PCLZIP_OPT_ADD_COMMENT, + PCLZIP_OPT_PREPEND_COMMENT. This will create, replace, add, or prepend comments + in the zip archive. + - When merging two archives, the comments are not any more lost, but merged, with a + blank space separator. + - Corrected bug : Files are not deleted when all files are asked to be deleted. + - Corrected bug : Folders with name '0' made PclZip to abort the create or add feature. + + + Version 2.0 : + ***** Warning : Some new features may break the backward compatibility for your scripts. + Please carefully read the readme file. + - Add the ability to delete by Index, name and regular expression. This feature is + performed by the method delete(), which uses the optional parameters + PCLZIP_OPT_BY_INDEX, PCLZIP_OPT_BY_NAME, PCLZIP_OPT_BY_EREG or PCLZIP_OPT_BY_PREG. + - Add the ability to extract by regular expression. To extract by regexp you must use the method + extract(), with the option PCLZIP_OPT_BY_EREG or PCLZIP_OPT_BY_PREG + (depending if you want to use ereg() or preg_match() syntax) followed by the + regular expression pattern. + - Add the ability to extract by index, directly with the extract() method. This is a + code improvment of the extractByIndex() method. + - Add the ability to extract by name. To extract by name you must use the method + extract(), with the option PCLZIP_OPT_BY_NAME followed by the filename to + extract or an array of filenames to extract. To extract all a folder, use the folder + name rather than the filename with a '/' at the end. + - Add the ability to add files without compression. This is done with a new attribute + which is PCLZIP_OPT_NO_COMPRESSION. + - Add the attribute PCLZIP_OPT_EXTRACT_AS_STRING, which allow to extract a file directly + in a string without using any file (or temporary file). + - Add constant PCLZIP_SEPARATOR for static configuration of filename separators in a single string. + The default separator is now a comma (,) and not any more a blank space. + THIS BREAK THE BACKWARD COMPATIBILITY : Please check if this may have an impact with + your script. + - Improve algorythm performance by removing the use of temporary files when adding or + extracting files in an archive. + - Add (correct) detection of empty filename zipping. This can occurs when the removed + path is the same + as a zipped dir. The dir is not zipped (['status'] = filtered), only its content. + - Add better support for windows paths (thanks for help from manus@manusfreedom.com). + - Corrected bug : When the archive file already exists with size=0, the add() method + fails. Corrected in 2.0. + - Remove the use of OS_WINDOWS constant. Use php_uname() function rather. + - Control the order of index ranges in extract by index feature. + - Change the internal management of folders (better handling of internal flag). + + + Version 1.3 : + - Removing the double include check. This is now done by include_once() and require_once() + PHP directives. + - Changing the error handling mecanism : Remove the use of an external error library. + The former PclError...() functions are replaced by internal equivalent methods. + By changing the environment variable PCLZIP_ERROR_EXTERNAL you can still use the former library. + Introducing the use of constants for error codes rather than integer values. This will help + in futur improvment. + Introduction of error handling functions like errorCode(), errorName() and errorInfo(). + - Remove the deprecated use of calling function with arguments passed by reference. + - Add the calling of extract(), extractByIndex(), create() and add() functions + with variable options rather than fixed arguments. + - Add the ability to remove all the file path while extracting or adding, + without any need to specify the path to remove. + This is available for extract(), extractByIndex(), create() and add() functionS by using + the new variable options parameters : + - PCLZIP_OPT_REMOVE_ALL_PATH : by indicating this option while calling the fct. + - Ability to change the mode of a file after the extraction (chmod()). + This is available for extract() and extractByIndex() functionS by using + the new variable options parameters. + - PCLZIP_OPT_SET_CHMOD : by setting the value of this option. + - Ability to definition call-back options. These call-back will be called during the adding, + or the extracting of file (extract(), extractByIndex(), create() and add() functions) : + - PCLZIP_CB_PRE_EXTRACT : will be called before each extraction of a file. The user + can trigerred the change the filename of the extracted file. The user can triggered the + skip of the extraction. This is adding a 'skipped' status in the file list result value. + - PCLZIP_CB_POST_EXTRACT : will be called after each extraction of a file. + Nothing can be triggered from that point. + - PCLZIP_CB_PRE_ADD : will be called before each add of a file. The user + can trigerred the change the stored filename of the added file. The user can triggered the + skip of the add. This is adding a 'skipped' status in the file list result value. + - PCLZIP_CB_POST_ADD : will be called after each add of a file. + Nothing can be triggered from that point. + - Two status are added in the file list returned as function result : skipped & filename_too_long + 'skipped' is used when a call-back function ask for skipping the file. + 'filename_too_long' is used while adding a file with a too long filename to archive (the file is + not added) + - Adding the function PclZipUtilPathInclusion(), that check the inclusion of a path into + a directory. + - Add a check of the presence of the archive file before some actions (like list, ...) + - Add the initialisation of field "index" in header array. This means that by + default index will be -1 when not explicitly set by the methods. + + Version 1.2 : + - Adding a duplicate function. + - Adding a merge function. The merge function is a "quick merge" function, + it just append the content of an archive at the end of the first one. There + is no check for duplicate files or more recent files. + - Improve the search of the central directory end. + + Version 1.1.2 : + + - Changing the license of PclZip. PclZip is now released under the GNU / LGPL license + (see License section). + - Adding the optional support of a static temporary directory. You will need to configure + the constant PCLZIP_TEMPORARY_DIR if you want to use this feature. + - Improving the rename() function. In some cases rename() does not work (different + Filesystems), so it will be replaced by a copy() + unlink() functions. + + Version 1.1.1 : + + - Maintenance release, no new feature. + + Version 1.1 : + + - New method Add() : adding files in the archive + - New method ExtractByIndex() : partial extract of the archive, files are identified by + their index in the archive + - New method DeleteByIndex() : delete some files/folder entries from the archive, + files are identified by their index in the archive. + - Adding a test of the zlib extension presence. If not present abort the script. + + Version 1.0.1 : + + - No new feature + + +3 - Corrected bugs +================== + + Corrected in Version 2.0 : + - Corrected : During an extraction, if a call-back fucntion is used and try to skip + a file, all the extraction process is stopped. + + Corrected in Version 1.3 : + - Corrected : Support of static synopsis for method extract() is broken. + - Corrected : invalid size of archive content field (0xFF) should be (0xFFFF). + - Corrected : When an extract is done with a remove_path parameter, the entry for + the directory with exactly the same path is not skipped/filtered. + - Corrected : extractByIndex() and deleteByIndex() were not managing index in the + right way. For example indexes '1,3-5,11' will only extract files 1 and 11. This + is due to a sort of the index resulting table that puts 11 before 3-5 (sort on + string and not interger). The sort is temporarilly removed, this means that + you must provide a sorted list of index ranges. + + Corrected in Version 1.2 : + + - Nothing. + + Corrected in Version 1.1.2 : + + - Corrected : Winzip is unable to delete or add new files in a PclZip created archives. + + Corrected in Version 1.1.1 : + + - Corrected : When archived file is not compressed (0% compression), the + extract method fails. + + Corrected in Version 1.1 : + + - Corrected : Adding a complete tree of folder may result in a bad archive + creation. + + Corrected in Version 1.0.1 : + + - Corrected : Error while compressing files greater than PCLZIP_READ_BLOCK_SIZE (default=1024). + + +4 - Known bugs or limitations +============================= + + Please publish bugs reports in SourceForge : + http://sourceforge.net/tracker/?group_id=40254&atid=427564 + + In Version 2.x : + - PclZip does only support file uncompressed or compressed with deflate (compression method 8) + - PclZip does not support password protected zip archive + - Some concern were seen when changing mtime of a file while archiving. + Seems to be linked to Daylight Saving Time (PclTest_changing_mtime). + + In Version 1.2 : + + - merge() methods does not check for duplicate files or last date of modifications. + + In Version 1.1 : + + - Limitation : Using 'extract' fields in the file header in the zip archive is not supported. + - WinZip is unable to delete a single file in a PclZip created archive. It is also unable to + add a file in a PclZip created archive. (Corrected in v.1.2) + + In Version 1.0.1 : + + - Adding a complete tree of folder may result in a bad archive + creation. (Corrected in V.1.1). + - Path given to methods must be in the unix format (/) and not the Windows format (\). + Workaround : Use only / directory separators. + - PclZip is using temporary files that are sometime the name of the file with a .tmp or .gz + added suffix. Files with these names may already exist and may be overwritten. + Workaround : none. + - PclZip does not check if the zlib extension is present. If it is absent, the zip + file is not created and the lib abort without warning. + Workaround : enable the zlib extension on the php install + + In Version 1.0 : + + - Error while compressing files greater than PCLZIP_READ_BLOCK_SIZE (default=1024). + (Corrected in v.1.0.1) + - Limitation : Multi-disk zip archive are not supported. + + +5 - License +=========== + + Since version 1.1.2, PclZip Library is released under GNU/LGPL license. + This library is free, so you can use it at no cost. + + HOWEVER, if you release a script, an application, a library or any kind of + code using PclZip library (or a part of it), YOU MUST : + - Indicate in the documentation (or a readme file), that your work + uses PclZip Library, and make a reference to the author and the web site + http://www.phpconcept.net + - Gives the ability to the final user to update the PclZip libary. + + I will also appreciate that you send me a mail (vincent@phpconcept.net), just to + be aware that someone is using PclZip. + + For more information about GNU/LGPL license : http://www.gnu.org + +6 - Warning +================= + + This library and the associated files are non commercial, non professional work. + It should not have unexpected results. However if any damage is caused by this software + the author can not be responsible. + The use of this software is at the risk of the user. + +7 - Documentation +================= + PclZip User Manuel is available in English on PhpConcept : http://www.phpconcept.net/pclzip/man/en/index.php + A Russian translation was done by Feskov Kuzma : http://php.russofile.ru/ru/authors/unsort/zip/ + +8 - Author +========== + + This software was written by Vincent Blavet (vincent@phpconcept.net) on its leasure time. + +9 - Contribute +============== + If you want to contribute to the development of PclZip, please contact vincent@phpconcept.net. + If you can help in financing PhpConcept hosting service, please go to + http://www.phpconcept.net/soutien.php diff --git a/trunk/lib/phpmailer/LICENSE b/trunk/lib/phpmailer/LICENSE new file mode 100644 index 0000000000..03851a3383 --- /dev/null +++ b/trunk/lib/phpmailer/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/trunk/lib/phpmailer/README b/trunk/lib/phpmailer/README new file mode 100644 index 0000000000..2d79f38622 --- /dev/null +++ b/trunk/lib/phpmailer/README @@ -0,0 +1,218 @@ +/******************************************************************* +* The http://phpmailer.codeworxtech.com/ website now carries a few * +* advertisements through the Google Adsense network. Please visit * +* the advertiser sites and help us offset some of our costs. * +* Thanks .... * +********************************************************************/ + +PHPMailer +Full Featured Email Transfer Class for PHP +========================================== + +Version 5.0.0 (April 02, 2009) + +With the release of this version, we are initiating a new version numbering +system to differentiate from the PHP4 version of PHPMailer. + +Most notable in this release is fully object oriented code. + +We now have available the PHPDocumentor (phpdocs) documentation. This is +separate from the regular download to keep file sizes down. Please see the +download area of http://phpmailer.codeworxtech.com. + +We also have created a new test script (see /test_script) that you can use +right out of the box. Copy the /test_script folder directly to your server (in +the same structure ... with class.phpmailer.php and class.smtp.php in the +folder above it. Then launch the test script with: +http://www.yourdomain.com/phpmailer/test_script/index.php +from this one script, you can test your server settings for mail(), sendmail (or +qmail), and SMTP. This will email you a sample email (using contents.html for +the email body) and two attachments. One of the attachments is used as an inline +image to demonstrate how PHPMailer will automatically detect if attachments are +the same source as inline graphics and only include one version. Once you click +the Submit button, the results will be displayed including any SMTP debug +information and send status. We will also display a version of the script that +you can cut and paste to include in your projects. Enjoy! + +Version 2.3 (November 08, 2008) + +We have removed the /phpdoc from the downloads. All documentation is now on +the http://phpmailer.codeworxtech.com website. + +The phpunit.php has been updated to support PHP5. + +For all other changes and notes, please see the changelog. + +Donations are accepted at PayPal with our id "paypal@worxteam.com". + +Version 2.2 (July 15 2008) + +- see the changelog. + +Version 2.1 (June 04 2008) + +With this release, we are announcing that the development of PHPMailer for PHP5 +will be our focus from this date on. We have implemented all the enhancements +and fixes from the latest release of PHPMailer for PHP4. + +Far more important, though, is that this release of PHPMailer (v2.1) is +fully tested with E_STRICT error checking enabled. + +** NOTE: WE HAVE A NEW LANGUAGE VARIABLE FOR DIGITALLY SIGNED S/MIME EMAILS. + IF YOU CAN HELP WITH LANGUAGES OTHER THAN ENGLISH AND SPANISH, IT WOULD BE + APPRECIATED. + +We have now added S/MIME functionality (ability to digitally sign emails). +BIG THANKS TO "sergiocambra" for posting this patch back in November 2007. +The "Signed Emails" functionality adds the Sign method to pass the private key +filename and the password to read it, and then email will be sent with +content-type multipart/signed and with the digital signature attached. + +A quick note on E_STRICT: + +- In about half the test environments the development version was subjected + to, an error was thrown for the date() functions (used at line 1565 and 1569). + This is NOT a PHPMailer error, it is the result of an incorrectly configured + PHP5 installation. The fix is to modify your 'php.ini' file and include the + date.timezone = America/New York + directive, (for your own server timezone) +- If you do get this error, and are unable to access your php.ini file, there is + a workaround. In your PHP script, add + date_default_timezone_set('America/Toronto'); + + * do NOT try to use + $myVar = date_default_timezone_get(); + as a test, it will throw an error. + +We have also included more example files to show the use of "sendmail", "mail()", +"smtp", and "gmail". + +We are also looking for more programmers to join the volunteer development team. +If you have an interest in this, please let us know. + +Enjoy! + + +Version 2.1.0beta1 & beta2 + +please note, this is BETA software +** DO NOT USE THIS IN PRODUCTION OR LIVE PROJECTS +INTENDED STRICTLY FOR TESTING + +** NOTE: + +As of November 2007, PHPMailer has a new project team headed by industry +veteran Andy Prevost (codeworxtech). The first release in more than two +years will focus on fixes, adding ease-of-use enhancements, provide +basic compatibility with PHP4 and PHP5 using PHP5 backwards compatibility +features. A new release is planned before year-end 2007 that will provide +full compatiblity with PHP4 and PHP5, as well as more bug fixes. + +We are looking for project developers to assist in restoring PHPMailer to +its leadership position. Our goals are to simplify use of PHPMailer, provide +good documentation and examples, and retain backward compatibility to level +1.7.3 standards. + +If you are interested in helping out, visit http://sourceforge.net/projects/phpmailer +and indicate your interest. + +** + +http://phpmailer.sourceforge.net/ + +This software is licenced under the LGPL. Please read LICENSE for information on the +software availability and distribution. + +Class Features: +- Send emails with multiple TOs, CCs, BCCs and REPLY-TOs +- Redundant SMTP servers +- Multipart/alternative emails for mail clients that do not read HTML email +- Support for 8bit, base64, binary, and quoted-printable encoding +- Uses the same methods as the very popular AspEmail active server (COM) component +- SMTP authentication +- Native language support +- Word wrap, and more! + +Why you might need it: + +Many PHP developers utilize email in their code. The only PHP function +that supports this is the mail() function. However, it does not expose +any of the popular features that many email clients use nowadays like +HTML-based emails and attachments. There are two proprietary +development tools out there that have all the functionality built into +easy to use classes: AspEmail(tm) and AspMail. Both of these +programs are COM components only available on Windows. They are also a +little pricey for smaller projects. + +Since I do Linux development I�ve missed these tools for my PHP coding. +So I built a version myself that implements the same methods (object +calls) that the Windows-based components do. It is open source and the +LGPL license allows you to place the class in your proprietary PHP +projects. + + +Installation: + +Copy class.phpmailer.php into your php.ini include_path. If you are +using the SMTP mailer then place class.smtp.php in your path as well. +In the language directory you will find several files like +phpmailer.lang-en.php. If you look right before the .php extension +that there are two letters. These represent the language type of the +translation file. For instance "en" is the English file and "br" is +the Portuguese file. Chose the file that best fits with your language +and place it in the PHP include path. If your language is English +then you have nothing more to do. If it is a different language then +you must point PHPMailer to the correct translation. To do this, call +the PHPMailer SetLanguage method like so: + +// To load the Portuguese version +$mail->SetLanguage("br", "/optional/path/to/language/directory/"); + +That's it. You should now be ready to use PHPMailer! + + +A Simple Example: + +IsSMTP(); // set mailer to use SMTP +$mail->Host = "smtp1.example.com;smtp2.example.com"; // specify main and backup server +$mail->SMTPAuth = true; // turn on SMTP authentication +$mail->Username = "jswan"; // SMTP username +$mail->Password = "secret"; // SMTP password + +$mail->From = "from@example.com"; +$mail->FromName = "Mailer"; +$mail->AddAddress("josh@example.net", "Josh Adams"); +$mail->AddAddress("ellen@example.com"); // name is optional +$mail->AddReplyTo("info@example.com", "Information"); + +$mail->WordWrap = 50; // set word wrap to 50 characters +$mail->AddAttachment("/var/tmp/file.tar.gz"); // add attachments +$mail->AddAttachment("/tmp/image.jpg", "new.jpg"); // optional name +$mail->IsHTML(true); // set email format to HTML + +$mail->Subject = "Here is the subject"; +$mail->Body = "This is the HTML message body in bold!"; +$mail->AltBody = "This is the body in plain text for non-HTML mail clients"; + +if(!$mail->Send()) +{ + echo "Message could not be sent.

    "; + echo "Mailer Error: " . $mail->ErrorInfo; + exit; +} + +echo "Message has been sent"; +?> + +CHANGELOG + +See ChangeLog.txt + +Download: http://sourceforge.net/project/showfiles.php?group_id=26031 + +Andy Prevost diff --git a/trunk/lib/phpmailer/class.pop3.php b/trunk/lib/phpmailer/class.pop3.php new file mode 100644 index 0000000000..f9fd3b2edb --- /dev/null +++ b/trunk/lib/phpmailer/class.pop3.php @@ -0,0 +1,407 @@ +pop_conn = 0; + $this->connected = false; + $this->error = null; + } + + /** + * Combination of public events - connect, login, disconnect + * @access public + * @param string $host + * @param integer $port + * @param integer $tval + * @param string $username + * @param string $password + */ + public function Authorise ($host, $port = false, $tval = false, $username, $password, $debug_level = 0) { + $this->host = $host; + + // If no port value is passed, retrieve it + if ($port == false) { + $this->port = $this->POP3_PORT; + } else { + $this->port = $port; + } + + // If no port value is passed, retrieve it + if ($tval == false) { + $this->tval = $this->POP3_TIMEOUT; + } else { + $this->tval = $tval; + } + + $this->do_debug = $debug_level; + $this->username = $username; + $this->password = $password; + + // Refresh the error log + $this->error = null; + + // Connect + $result = $this->Connect($this->host, $this->port, $this->tval); + + if ($result) { + $login_result = $this->Login($this->username, $this->password); + + if ($login_result) { + $this->Disconnect(); + + return true; + } + + } + + // We need to disconnect regardless if the login succeeded + $this->Disconnect(); + + return false; + } + + /** + * Connect to the POP3 server + * @access public + * @param string $host + * @param integer $port + * @param integer $tval + * @return boolean + */ + public function Connect ($host, $port = false, $tval = 30) { + // Are we already connected? + if ($this->connected) { + return true; + } + + /* + On Windows this will raise a PHP Warning error if the hostname doesn't exist. + Rather than supress it with @fsockopen, let's capture it cleanly instead + */ + + set_error_handler(array(&$this, 'catchWarning')); + + // Connect to the POP3 server + $this->pop_conn = fsockopen($host, // POP3 Host + $port, // Port # + $errno, // Error Number + $errstr, // Error Message + $tval); // Timeout (seconds) + + // Restore the error handler + restore_error_handler(); + + // Does the Error Log now contain anything? + if ($this->error && $this->do_debug >= 1) { + $this->displayErrors(); + } + + // Did we connect? + if ($this->pop_conn == false) { + // It would appear not... + $this->error = array( + 'error' => "Failed to connect to server $host on port $port", + 'errno' => $errno, + 'errstr' => $errstr + ); + + if ($this->do_debug >= 1) { + $this->displayErrors(); + } + + return false; + } + + // Increase the stream time-out + + // Check for PHP 4.3.0 or later + if (version_compare(phpversion(), '5.0.0', 'ge')) { + stream_set_timeout($this->pop_conn, $tval, 0); + } else { + // Does not work on Windows + if (substr(PHP_OS, 0, 3) !== 'WIN') { + socket_set_timeout($this->pop_conn, $tval, 0); + } + } + + // Get the POP3 server response + $pop3_response = $this->getResponse(); + + // Check for the +OK + if ($this->checkResponse($pop3_response)) { + // The connection is established and the POP3 server is talking + $this->connected = true; + return true; + } + + } + + /** + * Login to the POP3 server (does not support APOP yet) + * @access public + * @param string $username + * @param string $password + * @return boolean + */ + public function Login ($username = '', $password = '') { + if ($this->connected == false) { + $this->error = 'Not connected to POP3 server'; + + if ($this->do_debug >= 1) { + $this->displayErrors(); + } + } + + if (empty($username)) { + $username = $this->username; + } + + if (empty($password)) { + $password = $this->password; + } + + $pop_username = "USER $username" . $this->CRLF; + $pop_password = "PASS $password" . $this->CRLF; + + // Send the Username + $this->sendString($pop_username); + $pop3_response = $this->getResponse(); + + if ($this->checkResponse($pop3_response)) { + // Send the Password + $this->sendString($pop_password); + $pop3_response = $this->getResponse(); + + if ($this->checkResponse($pop3_response)) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + /** + * Disconnect from the POP3 server + * @access public + */ + public function Disconnect () { + $this->sendString('QUIT'); + + fclose($this->pop_conn); + } + + ///////////////////////////////////////////////// + // Private Methods + ///////////////////////////////////////////////// + + /** + * Get the socket response back. + * $size is the maximum number of bytes to retrieve + * @access private + * @param integer $size + * @return string + */ + private function getResponse ($size = 128) { + $pop3_response = fgets($this->pop_conn, $size); + + return $pop3_response; + } + + /** + * Send a string down the open socket connection to the POP3 server + * @access private + * @param string $string + * @return integer + */ + private function sendString ($string) { + $bytes_sent = fwrite($this->pop_conn, $string, strlen($string)); + + return $bytes_sent; + } + + /** + * Checks the POP3 server response for +OK or -ERR + * @access private + * @param string $string + * @return boolean + */ + private function checkResponse ($string) { + if (substr($string, 0, 3) !== '+OK') { + $this->error = array( + 'error' => "Server reported an error: $string", + 'errno' => 0, + 'errstr' => '' + ); + + if ($this->do_debug >= 1) { + $this->displayErrors(); + } + + return false; + } else { + return true; + } + + } + + /** + * If debug is enabled, display the error message array + * @access private + */ + private function displayErrors () { + echo '

    ';
    +
    +    foreach ($this->error as $single_error) {
    +      print_r($single_error);
    +    }
    +
    +    echo '
    '; + } + + /** + * Takes over from PHP for the socket warning handler + * @access private + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + */ + private function catchWarning ($errno, $errstr, $errfile, $errline) { + $this->error[] = array( + 'error' => "Connecting to the POP3 server raised a PHP warning: ", + 'errno' => $errno, + 'errstr' => $errstr + ); + } + + // End of class +} +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/class.smtp.php b/trunk/lib/phpmailer/class.smtp.php new file mode 100644 index 0000000000..c664d971eb --- /dev/null +++ b/trunk/lib/phpmailer/class.smtp.php @@ -0,0 +1,814 @@ +smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to the server specified on the port specified. + * If the port is not specified use the default SMTP_PORT. + * If tval is specified then a connection will try and be + * established with the server for that number of seconds. + * If tval is not specified the default is 30 seconds to + * try on the connection. + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @return bool + */ + public function Connect($host, $port = 0, $tval = 30) { + // set the error val to null so there is no confusion + $this->error = null; + + // make sure we are __not__ connected + if($this->connected()) { + // already connected, generate error + $this->error = array("error" => "Already connected to a server"); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // connect to the smtp server + $this->smtp_conn = @fsockopen($host, // the host of the server + $port, // the port to use + $errno, // error number if any + $errstr, // error message if any + $tval); // give up after ? secs + // verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array("error" => "Failed to connect to server", + "errno" => $errno, + "errstr" => $errstr); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '
    '; + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != "WIN") + socket_set_timeout($this->smtp_conn, $tval, 0); + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $announce . $this->CRLF . '
    '; + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array("error" => "Called StartTLS() without being connected"); + return false; + } + + fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 220) { + $this->error = + array("error" => "STARTTLS not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @return bool + */ + public function Authenticate($username, $password) { + // Start authentication + fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "AUTH not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + // Send encoded username + fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "Username not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + // Send encoded password + fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 235) { + $this->error = + array("error" => "Password not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + return true; + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = socket_get_status($this->smtp_conn); + if($sock_status["eof"]) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE:" . $this->CRLF . "EOF caught while checking if connected"; + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being seperated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552,554,451,452 + * SMTP CODE FAILURE: 451,554 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Data() without being connected"); + return false; + } + + fputs($this->smtp_conn,"DATA" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 354) { + $this->error = + array("error" => "DATA command not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n","\n",$msg_data); + $msg_data = str_replace("\r","\n",$msg_data); + $lines = explode("\n",$msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0],0,strpos($lines[0],":")); + $in_headers = false; + if(!empty($field) && !strstr($field," ")) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(,$line) = @each($lines)) { + $lines_out = null; + if($line == "" && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line,0,$max_line_length)," "); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos); + } else { + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(,$line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == ".") { + $line_out = "." . $line_out; + } + } + fputs($this->smtp_conn,$line_out . $this->CRLF); + } + } + + // message data has been sent + fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 250) { + $this->error = + array("error" => "DATA not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Hello() without being connected"); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = "localhost"; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello("EHLO", $host)) { + if(!$this->SendHello("HELO", $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access private + * @return bool + */ + private function SendHello($hello, $host) { + fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER: " . $rply . $this->CRLF . '
    '; + } + + if($code != 250) { + $this->error = + array("error" => $hello . " not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,421 + * @access public + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Mail() without being connected"); + return false; + } + + $useVerp = ($this->do_verp ? "XVERP" : ""); + fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 250) { + $this->error = + array("error" => "MAIL not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Quit() without being connected"); + return false; + } + + // send the quit command to the server + fputs($this->smtp_conn,"quit" . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $byemsg . $this->CRLF . '
    '; + } + + $rval = true; + $e = null; + + $code = substr($byemsg,0,3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array("error" => "SMTP server rejected quit command", + "smtp_code" => $code, + "smtp_rply" => substr($byemsg,4)); + $rval = false; + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $e["error"] . ": " . $byemsg . $this->CRLF . '
    '; + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250,251 + * SMTP CODE FAILURE: 550,551,552,553,450,451,452 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Recipient() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 250 && $code != 251) { + $this->error = + array("error" => "RCPT not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500,501,504,421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Reset() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RSET" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 250) { + $this->error = + array("error" => "RSET failed", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,502,421 + * @access public + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called SendAndMail() without being connected"); + return false; + } + + fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
    '; + } + + if($code != 250) { + $this->error = + array("error" => "SAML not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
    '; + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array("error" => "This method, TURN, of the SMTP ". + "is not implemented"); + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF . '
    '; + } + return false; + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access private + * @return string + */ + private function get_lines() { + $data = ""; + while($str = @fgets($this->smtp_conn,515)) { + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
    '; + echo "SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
    '; + } + $data .= $str; + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '
    '; + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str,3,1) == " ") { break; } + } + return $data; + } + +} + +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-ar.php b/trunk/lib/phpmailer/language/phpmailer.lang-ar.php new file mode 100644 index 0000000000..b7c5057d0c --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-ar.php @@ -0,0 +1,27 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: لم نستطع تأكيد الهوية.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: لم نستطع الاتصال بمخدم SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: لم يتم قبول المعلومات .'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'ترميز غير معروف: '; +$PHPMAILER_LANG['execute'] = 'لم أستطع تنفيذ : '; +$PHPMAILER_LANG['file_access'] = 'لم نستطع الوصول للملف: '; +$PHPMAILER_LANG['file_open'] = 'File Error: لم نستطع فتح الملف: '; +$PHPMAILER_LANG['from_failed'] = 'البريد التالي لم نستطع ارسال البريد له : '; +$PHPMAILER_LANG['instantiate'] = 'لم نستطع توفير خدمة البريد.'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer غير مدعوم.'; +//$PHPMAILER_LANG['provide_address'] = 'You must provide at least one recipient email address.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: الأخطاء التالية ' . + 'فشل في الارسال لكل من : '; +$PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-br.php b/trunk/lib/phpmailer/language/phpmailer.lang-br.php new file mode 100644 index 0000000000..6afe60b18c --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-br.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-ca.php b/trunk/lib/phpmailer/language/phpmailer.lang-ca.php new file mode 100644 index 0000000000..4a160a21eb --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-ca.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-ch.php b/trunk/lib/phpmailer/language/phpmailer.lang-ch.php new file mode 100644 index 0000000000..31ebd861cb --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-ch.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-cz.php b/trunk/lib/phpmailer/language/phpmailer.lang-cz.php new file mode 100644 index 0000000000..1c8b206392 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-cz.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-de.php b/trunk/lib/phpmailer/language/phpmailer.lang-de.php new file mode 100644 index 0000000000..b2a76ce1b5 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-de.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-dk.php b/trunk/lib/phpmailer/language/phpmailer.lang-dk.php new file mode 100644 index 0000000000..b26257316b --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-dk.php @@ -0,0 +1,26 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Kunne ikke logge på.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Kunne ikke tilslutte SMTP serveren.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data kunne ikke accepteres.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunne ikke køre: '; +$PHPMAILER_LANG['file_access'] = 'Ingen adgang til fil: '; +$PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: '; +$PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: '; +$PHPMAILER_LANG['instantiate'] = 'Kunne ikke initialisere email funktionen.'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.'; +$PHPMAILER_LANG['provide_address'] = 'Du skal indtaste mindst en modtagers emailadresse.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere er forkerte: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-es.php b/trunk/lib/phpmailer/language/phpmailer.lang-es.php new file mode 100644 index 0000000000..69b6817482 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-es.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-et.php b/trunk/lib/phpmailer/language/phpmailer.lang-et.php new file mode 100644 index 0000000000..cf61779b08 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-et.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-fi.php b/trunk/lib/phpmailer/language/phpmailer.lang-fi.php new file mode 100644 index 0000000000..12a845aad6 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-fi.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-fo.php b/trunk/lib/phpmailer/language/phpmailer.lang-fo.php new file mode 100644 index 0000000000..6bd9b0a213 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-fo.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-fr.php b/trunk/lib/phpmailer/language/phpmailer.lang-fr.php new file mode 100644 index 0000000000..c99ac3caf9 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-fr.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-hu.php b/trunk/lib/phpmailer/language/phpmailer.lang-hu.php new file mode 100644 index 0000000000..caca0b50f1 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-hu.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-it.php b/trunk/lib/phpmailer/language/phpmailer.lang-it.php new file mode 100644 index 0000000000..fc1fcb8d2e --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-it.php @@ -0,0 +1,27 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Impossibile autenticarsi.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Impossibile connettersi all\'host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Data non accettati dal server.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Encoding set dei caratteri sconosciuto: '; +$PHPMAILER_LANG['execute'] = 'Impossibile eseguire l\'operazione: '; +$PHPMAILER_LANG['file_access'] = 'Impossibile accedere al file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Impossibile aprire il file: '; +$PHPMAILER_LANG['from_failed'] = 'I seguenti indirizzi mittenti hanno generato errore: '; +$PHPMAILER_LANG['instantiate'] = 'Impossibile istanziare la funzione mail'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['provide_address'] = 'Deve essere fornito almeno un indirizzo ricevente'; +$PHPMAILER_LANG['mailer_not_supported'] = 'Mailer non supportato'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: I seguenti indirizzi destinatari hanno generato errore: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-ja.php b/trunk/lib/phpmailer/language/phpmailer.lang-ja.php new file mode 100644 index 0000000000..63cfb23b6a --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-ja.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-nl.php b/trunk/lib/phpmailer/language/phpmailer.lang-nl.php new file mode 100644 index 0000000000..d2c380b09d --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-nl.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-no.php b/trunk/lib/phpmailer/language/phpmailer.lang-no.php new file mode 100644 index 0000000000..65cb884399 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-no.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-pl.php b/trunk/lib/phpmailer/language/phpmailer.lang-pl.php new file mode 100644 index 0000000000..f4fd801d61 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-pl.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-ro.php b/trunk/lib/phpmailer/language/phpmailer.lang-ro.php new file mode 100644 index 0000000000..f6aa922556 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-ro.php @@ -0,0 +1,27 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'Eroare SMTP: Nu a functionat autentificarea.'; +$PHPMAILER_LANG['connect_host'] = 'Eroare SMTP: Nu m-am putut conecta la adresa SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Eroare SMTP: Continutul mailului nu a fost acceptat.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Encodare necunoscuta: '; +$PHPMAILER_LANG['execute'] = 'Nu pot executa: '; +$PHPMAILER_LANG['file_access'] = 'Nu pot accesa fisierul: '; +$PHPMAILER_LANG['file_open'] = 'Eroare de fisier: Nu pot deschide fisierul: '; +$PHPMAILER_LANG['from_failed'] = 'Urmatoarele adrese From au dat eroare: '; +$PHPMAILER_LANG['instantiate'] = 'Nu am putut instantia functia mail.'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nu este suportat.'; +$PHPMAILER_LANG['provide_address'] = 'Trebuie sa adaugati cel putin un recipient (adresa de mail).'; +$PHPMAILER_LANG['recipients_failed'] = 'Eroare SMTP: Urmatoarele adrese de mail au dat eroare: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-ru.php b/trunk/lib/phpmailer/language/phpmailer.lang-ru.php new file mode 100644 index 0000000000..d6990525de --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-ru.php @@ -0,0 +1,25 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к серверу SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Неизвестный вид кодировки: '; +$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; +$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удается открыть файл: '; +$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; +$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail.'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один адрес e-mail получателя.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: отправка по следующим адресам получателей не удалась: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-se.php b/trunk/lib/phpmailer/language/phpmailer.lang-se.php new file mode 100644 index 0000000000..67e05f59c6 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-se.php @@ -0,0 +1,26 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fel: Kunde inte autentisera.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fel: Kunde inte ansluta till SMTP-server.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fel: Data accepterades inte.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Okänt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunde inte köra: '; +$PHPMAILER_LANG['file_access'] = 'Ingen åtkomst till fil: '; +$PHPMAILER_LANG['file_open'] = 'Fil fel: Kunde inte öppna fil: '; +$PHPMAILER_LANG['from_failed'] = 'Följande avsändaradress är felaktig: '; +$PHPMAILER_LANG['instantiate'] = 'Kunde inte initiera e-postfunktion.'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['provide_address'] = 'Du måste ange minst en mottagares e-postadress.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer stöds inte.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fel: Följande mottagare är felaktig: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-tr.php b/trunk/lib/phpmailer/language/phpmailer.lang-tr.php new file mode 100644 index 0000000000..d24627a49a --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-tr.php @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-zh-cn.php b/trunk/lib/phpmailer/language/phpmailer.lang-zh-cn.php new file mode 100644 index 0000000000..ba73c9f33b --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-zh-cn.php @@ -0,0 +1,26 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。\n请检查您设置的用户名密码是否正确。\n有的系统登录用户名是完整的邮箱地址。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。\n请确认禅道所在的机器能够访问smtp服务器,\n确认域名解析是否正常,端口号是否正确,防火墙是否正常。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; +//$P$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = '未知编码: '; +$PHPMAILER_LANG['execute'] = '无法执行:'; +$PHPMAILER_LANG['file_access'] = '无法访问文件:'; +$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; +$PHPMAILER_LANG['from_failed'] = '发送地址错误:'; +$PHPMAILER_LANG['instantiate'] = '未知函数调用。'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。'; +$PHPMAILER_LANG['provide_address'] = '收件人没有设置邮箱,请到组织视图中检查下相应用户的email设置。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> diff --git a/trunk/lib/phpmailer/language/phpmailer.lang-zh-tw.php b/trunk/lib/phpmailer/language/phpmailer.lang-zh-tw.php new file mode 100644 index 0000000000..fef66f8cb1 --- /dev/null +++ b/trunk/lib/phpmailer/language/phpmailer.lang-zh-tw.php @@ -0,0 +1,26 @@ + +*/ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 錯誤:登錄失敗。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 錯誤:無法連接到 SMTP 主機。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 錯誤:數據不被接受。'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = '未知編碼: '; +$PHPMAILER_LANG['file_access'] = '無法訪問文件:'; +$PHPMAILER_LANG['file_open'] = '文件錯誤:無法打開文件:'; +$PHPMAILER_LANG['from_failed'] = '發送地址錯誤:'; +$PHPMAILER_LANG['execute'] = '無法執行:'; +$PHPMAILER_LANG['instantiate'] = '未知函數調用。'; +//$PHPMAILER_LANG['invalid_email'] = 'Not sending, email address is invalid: '; +$PHPMAILER_LANG['provide_address'] = '必須提供至少一個收件人地址。'; +$PHPMAILER_LANG['mailer_not_supported'] = '發信客戶端不被支持。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:收件人地址錯誤:'; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +?> \ No newline at end of file diff --git a/trunk/lib/phpmailer/phpmailer.class.php b/trunk/lib/phpmailer/phpmailer.class.php new file mode 100644 index 0000000000..09479239bf --- /dev/null +++ b/trunk/lib/phpmailer/phpmailer.class.php @@ -0,0 +1,2320 @@ +exceptions = ($exceptions == true); + } + + /** + * Sets message type to HTML. + * @param bool $ishtml + * @return void + */ + public function IsHTML($ishtml = true) { + if ($ishtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Sets Mailer to send message using SMTP. + * @return void + */ + public function IsSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * @return void + */ + public function IsMail() { + $this->Mailer = 'mail'; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * @return void + */ + public function IsSendmail() { + if (!stristr(ini_get('sendmail_path'), 'sendmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + /** + * Sets Mailer to send message using the qmail MTA. + * @return void + */ + public function IsQmail() { + if (stristr(ini_get('sendmail_path'), 'qmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + ///////////////////////////////////////////////// + // METHODS, RECIPIENTS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddAddress($address, $name = '') { + return $this->AddAnAddress('to', $address, $name); + } + + /** + * Adds a "Cc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddCC($address, $name = '') { + return $this->AddAnAddress('cc', $address, $name); + } + + /** + * Adds a "Bcc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddBCC($address, $name = '') { + return $this->AddAnAddress('bcc', $address, $name); + } + + /** + * Adds a "Reply-to" address. + * @param string $address + * @param string $name + * @return boolean + */ + public function AddReplyTo($address, $name = '') { + return $this->AddAnAddress('ReplyTo', $address, $name); + } + + /** + * Adds an address to one of the recipient arrays + * Addresses that have been added already return false, but do not throw exceptions + * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' + * @param string $address The email address to send to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + * @access private + */ + private function AddAnAddress($kind, $address, $name = '') { + if (!preg_match('/^(to|cc|bcc|ReplyTo)$/', $kind)) { + echo 'Invalid recipient array: ' . kind; + return false; + } + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!self::ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + echo $this->Lang('invalid_address').': '.$address; + return false; + } + if ($kind != 'ReplyTo') { + if (!isset($this->all_recipients[strtolower($address)])) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; +} + +/** + * Set the From and FromName properties + * @param string $address + * @param string $name + * @return boolean + */ + public function SetFrom($address, $name = '',$auto=1) { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!self::ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + echo $this->Lang('invalid_address').': '.$address; + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->ReplyTo)) { + $this->AddAnAddress('ReplyTo', $address, $name); + } + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Check that a string looks roughly like an email address should + * Static so it can be used without instantiation + * Tries to use PHP built-in validator in the filter extension (from PHP 5.2), falls back to a reasonably competent regex validator + * Conforms approximately to RFC2822 + * @link http://www.hexillion.com/samples/#Regex Original pattern found here + * @param string $address The email address to check + * @return boolean + * @static + * @access public + */ + public static function ValidateAddress($address) { + if (function_exists('filter_var')) { //Introduced in PHP 5.2 + if(filter_var($address, FILTER_VALIDATE_EMAIL) === FALSE) { + return false; + } else { + return true; + } + } else { + return preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $address); + } + } + + ///////////////////////////////////////////////// + // METHODS, MAIL SENDING + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. + * @return bool + */ + public function Send() { + try { + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL); + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) { + $this->ContentType = 'multipart/alternative'; + } + + $this->error_count = 0; // reset errors + $this->SetMessageType(); + $header = $this->CreateHeader(); + $body = $this->CreateBody(); + + if (empty($this->Body)) { + throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL); + } + + // digitally sign with DKIM if enabled + if ($this->DKIM_domain && $this->DKIM_private) { + $header_dkim = $this->DKIM_Add($header,$this->Subject,$body); + $header = str_replace("\r\n","\n",$header_dkim) . $header; + } + + // Choose the mailer and send through it + switch($this->Mailer) { + case 'sendmail': + return $this->SendmailSend($header, $body); + case 'smtp': + return $this->SmtpSend($header, $body); + default: + return $this->MailSend($header, $body); + } + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + echo $e->getMessage()."\n"; + return false; + } + } + + /** + * Sends mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @access protected + * @return bool + */ + protected function SendmailSend($header, $body) { + if ($this->Sender != '') { + $sendmail = sprintf("%s -oi -f %s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); + } + if ($this->SingleTo === true) { + foreach ($this->SingleToArray as $key => $val) { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, "To: " . $val . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent,$val,$this->cc,$this->bcc,$this->Subject,$body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent,$this->to,$this->cc,$this->bcc,$this->Subject,$body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Sends mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @access protected + * @return bool + */ + protected function MailSend($header, $body) { + $toArr = array(); + foreach($this->to as $t) { + $toArr[] = $this->AddrFormat($t); + } + $to = implode(', ', $toArr); + + $params = sprintf("-oi -f %s", $this->Sender); + if ($this->Sender != '' && strlen(ini_get('safe_mode'))< 1) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $key => $val) { + $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent,$val,$this->cc,$this->bcc,$this->Subject,$body); + } + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent,$to,$this->cc,$this->bcc,$this->Subject,$body); + } + } else { + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $key => $val) { + $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent,$val,$this->cc,$this->bcc,$this->Subject,$body); + } + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent,$to,$this->cc,$this->bcc,$this->Subject,$body); + } + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if(!$rt) { + throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * @param string $header The message headers + * @param string $body The message body + * @uses SMTP + * @access protected + * @return bool + */ + protected function SmtpSend($header, $body) { + require_once $this->PluginDir . 'class.smtp.php'; + $bad_rcpt = array(); + + if(!$this->SmtpConnect()) { + throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; + if(!$this->smtp->Mail($smtp_from)) { + throw new phpmailerException($this->Lang('from_failed') . $smtp_from, self::STOP_CRITICAL); + } + + // Attempt to send attach all recipients + foreach($this->to as $to) { + if (!$this->smtp->Recipient($to[0])) { + $bad_rcpt[] = $to[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent,$to[0],'','',$this->Subject,$body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent,$to[0],'','',$this->Subject,$body); + } + } + foreach($this->cc as $cc) { + if (!$this->smtp->Recipient($cc[0])) { + $bad_rcpt[] = $cc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent,'',$cc[0],'',$this->Subject,$body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent,'',$cc[0],'',$this->Subject,$body); + } + } + foreach($this->bcc as $bcc) { + if (!$this->smtp->Recipient($bcc[0])) { + $bad_rcpt[] = $bcc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent,'','',$bcc[0],$this->Subject,$body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent,'','',$bcc[0],$this->Subject,$body); + } + } + + + if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses + $badaddresses = implode(', ', $bad_rcpt); + throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses); + } + if(!$this->smtp->Data($header . $body)) { + throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL); + } + if($this->SMTPKeepAlive == true) { + $this->smtp->Reset(); + } + return true; + } + + /** + * Initiates a connection to an SMTP server. + * Returns false if the operation failed. + * @uses SMTP + * @access public + * @return bool + */ + public function SmtpConnect() { + if(is_null($this->smtp)) { + $this->smtp = new SMTP(); + } + + $this->smtp->do_debug = $this->SMTPDebug; + $hosts = explode(';', $this->Host); + $index = 0; + $connection = $this->smtp->Connected(); + + // Retry while there is no connection + try { + while($index < count($hosts) && !$connection) { + $hostinfo = array(); + if (preg_match('/^(.+):([0-9]+)$/', $hosts[$index], $hostinfo)) { + $host = $hostinfo[1]; + $port = $hostinfo[2]; + } else { + $host = $hosts[$index]; + $port = $this->Port; + } + + $tls = ($this->SMTPSecure == 'tls'); + $ssl = ($this->SMTPSecure == 'ssl'); + + if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) { + + $hello = ($this->Helo != '' ? $this->Helo : $this->ServerHostname()); + $this->smtp->Hello($hello); + + if ($tls) { + if (!$this->smtp->StartTLS()) { + throw new phpmailerException($this->Lang('tls')); + } + + //We must resend HELO after tls negotiation + $this->smtp->Hello($hello); + } + + $connection = true; + if ($this->SMTPAuth) { + if (!$this->smtp->Authenticate($this->Username, $this->Password)) { + throw new phpmailerException($this->Lang('authenticate')); + } + } + } + $index++; + if (!$connection) { + throw new phpmailerException($this->Lang('connect_host')); + } + } + } catch (phpmailerException $e) { + $this->smtp->Reset(); + throw $e; + } + return true; + } + + /** + * Closes the active SMTP session if one exists. + * @return void + */ + public function SmtpClose() { + if(!is_null($this->smtp)) { + if($this->smtp->Connected()) { + $this->smtp->Quit(); + $this->smtp->Close(); + } + } + } + + /** + * Sets the language for all class error messages. + * Returns false if it cannot load the language file. The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br") + * @param string $lang_path Path to the language file directory + * @access public + */ + function SetLanguage($langcode = 'en', $lang_path = 'language/') { + //Define full set of translatable strings + $PHPMAILER_LANG = array( + 'provide_address' => 'You must provide at least one recipient email address.', + 'mailer_not_supported' => ' mailer is not supported.', + 'execute' => 'Could not execute: ', + 'instantiate' => 'Could not instantiate mail function.', + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'from_failed' => 'The following From address failed: ', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'data_not_accepted' => 'SMTP Error: Data not accepted.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'encoding' => 'Unknown encoding: ', + 'signing' => 'Signing Error: ', + 'smtp_error' => 'SMTP server error: ', + 'empty_message' => 'Message body empty', + 'invalid_address' => 'Invalid address', + 'variable_set' => 'Cannot set or reset variable: ' + ); + //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"! + $l = true; + if ($langcode != 'en') { //There is no English translation file + $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php'; + } + $this->language = $PHPMAILER_LANG; + return ($l == true); //Returns false if language not found + } + + /** + * Return the current array of language strings + * @return array + */ + public function GetTranslations() { + return $this->language; + } + + ///////////////////////////////////////////////// + // METHODS, MESSAGE CREATION + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. + * @access public + * @return string + */ + public function AddrAppend($type, $addr) { + $addr_str = $type . ': '; + $addresses = array(); + foreach ($addr as $a) { + $addresses[] = $this->AddrFormat($a); + } + $addr_str .= implode(', ', $addresses); + $addr_str .= $this->LE; + + return $addr_str; + } + + /** + * Formats an address correctly. + * @access public + * @return string + */ + public function AddrFormat($addr) { + if (empty($addr[1])) { + return $this->SecureHeader($addr[0]); + } else { + return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">"; + } + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == "utf-8"); + + $message = $this->FixEOL($message); + if (substr($message, -1) == $this->LE) { + $message = substr($message, 0, -1); + } + + $line = explode($this->LE, $message); + $message = ''; + for ($i=0 ;$i < count($line); $i++) { + $line_part = explode(' ', $line[$i]); + $buf = ''; + for ($e = 0; $e $length)) { + $space_left = $length - strlen($buf) - 1; + if ($e != 0) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf("=%s", $this->LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + $len = $length; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf("=%s", $this->LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (' ' . $word); + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . $this->LE; + } + + return $message; + } + + /** + * Finds last character boundary prior to maxLength in a utf-8 + * quoted (printable) encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param int $maxLength find last character boundary prior to this length + * @return int + */ + public function UTF8CharBoundary($encodedText, $maxLength) { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, "="); + if ($encodedCharPos !== false) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + $maxLength = ($encodedCharPos == 0) ? $maxLength : + $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec >= 192) { // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + + /** + * Set the body wrapping. + * @access public + * @return void + */ + public function SetWordWrap() { + if($this->WordWrap < 1) { + return; + } + + switch($this->message_type) { + case 'alt': + case 'alt_attachments': + $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->WrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assembles message header. + * @access public + * @return string The assembled header + */ + public function CreateHeader() { + $result = ''; + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $uniq_id; + $this->boundary[2] = 'b2_' . $uniq_id; + + $result .= $this->HeaderLine('Date', self::RFCDate()); + if($this->Sender == '') { + $result .= $this->HeaderLine('Return-Path', trim($this->From)); + } else { + $result .= $this->HeaderLine('Return-Path', trim($this->Sender)); + } + + // To be created automatically by mail() + if($this->Mailer != 'mail') { + if ($this->SingleTo === true) { + foreach($this->to as $t) { + $this->SingleToArray[] = $this->AddrFormat($t); + } + } else { + if(count($this->to) > 0) { + $result .= $this->AddrAppend('To', $this->to); + } elseif (count($this->cc) == 0) { + $result .= $this->HeaderLine('To', 'undisclosed-recipients:;'); + } + } + } + + $from = array(); + $from[0][0] = trim($this->From); + $from[0][1] = $this->FromName; + $result .= $this->AddrAppend('From', $from); + + // sendmail and mail() extract Cc from the header before sending + if(count($this->cc) > 0) { + $result .= $this->AddrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { + $result .= $this->AddrAppend('Bcc', $this->bcc); + } + + if(count($this->ReplyTo) > 0) { + $result .= $this->AddrAppend('Reply-to', $this->ReplyTo); + } + + // mail() sets the subject itself + if($this->Mailer != 'mail') { + $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject))); + } + + if($this->MessageID != '') { + $result .= $this->HeaderLine('Message-ID',$this->MessageID); + } else { + $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE); + } + $result .= $this->HeaderLine('X-Priority', $this->Priority); + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (phpmailer.sourceforge.net)'); + + if($this->ConfirmReadingTo != '') { + $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + } + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) { + $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1]))); + } + if (!$this->sign_key_file) { + $result .= $this->HeaderLine('MIME-Version', '1.0'); + $result .= $this->GetMailMIME(); + } + + return $result; + } + + /** + * Returns the message MIME. + * @access public + * @return string + */ + public function GetMailMIME() { + $result = ''; + switch($this->message_type) { + case 'plain': + $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); + $result .= sprintf("Content-Type: %s; charset=\"%s\"", $this->ContentType, $this->CharSet); + break; + case 'attachments': + case 'alt_attachments': + if($this->InlineImageExists()){ + $result .= sprintf("Content-Type: %s;%s\ttype=\"text/html\";%s\tboundary=\"%s\"%s", 'multipart/related', $this->LE, $this->LE, $this->boundary[1], $this->LE); + } else { + $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + } + break; + case 'alt': + $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + } + + if($this->Mailer != 'mail') { + $result .= $this->LE.$this->LE; + } + + return $result; + } + + /** + * Assembles the message body. Returns an empty string on failure. + * @access public + * @return string The assembled message body + */ + public function CreateBody() { + $body = ''; + + if ($this->sign_key_file) { + $body .= $this->GetMailMIME(); + } + + $this->SetWordWrap(); + + switch($this->message_type) { + case 'alt': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'plain': + $body .= $this->EncodeString($this->Body, $this->Encoding); + break; + case 'attachments': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE; + $body .= $this->AttachAll(); + break; + case 'alt_attachments': + $body .= sprintf("--%s%s", $this->boundary[1], $this->LE); + $body .= sprintf("Content-Type: %s;%s" . "\tboundary=\"%s\"%s", 'multipart/alternative', $this->LE, $this->boundary[2], $this->LE.$this->LE); + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', '') . $this->LE; // Create text body + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', '') . $this->LE; // Create the HTML body + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->AttachAll(); + break; + } + + if ($this->IsError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + $file = tempnam('', 'mail'); + file_put_contents($file, $body); //TODO check this worked + $signed = tempnam("", "signed"); + if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)) { + @unlink($file); + @unlink($signed); + $body = file_get_contents($signed); + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->Lang("signing").openssl_error_string()); + } + } catch (phpmailerException $e) { + $body = ''; + if ($this->exceptions) { + throw $e; + } + } + } + + return $body; + } + + /** + * Returns the start of a message boundary. + * @access private + */ + private function GetBoundary($boundary, $charSet, $contentType, $encoding) { + $result = ''; + if($charSet == '') { + $charSet = $this->CharSet; + } + if($contentType == '') { + $contentType = $this->ContentType; + } + if($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->TextLine('--' . $boundary); + $result .= sprintf("Content-Type: %s; charset = \"%s\"", $contentType, $charSet); + $result .= $this->LE; + $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding); + $result .= $this->LE; + + return $result; + } + + /** + * Returns the end of a message boundary. + * @access private + */ + private function EndBoundary($boundary) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Sets the message type. + * @access private + * @return void + */ + private function SetMessageType() { + if(count($this->attachment) < 1 && strlen($this->AltBody) < 1) { + $this->message_type = 'plain'; + } else { + if(count($this->attachment) > 0) { + $this->message_type = 'attachments'; + } + if(strlen($this->AltBody) > 0 && count($this->attachment) < 1) { + $this->message_type = 'alt'; + } + if(strlen($this->AltBody) > 0 && count($this->attachment) > 0) { + $this->message_type = 'alt_attachments'; + } + } + } + + /** + * Returns a formatted header line. + * @access public + * @return string + */ + public function HeaderLine($name, $value) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Returns a formatted mail line. + * @access public + * @return string + */ + public function TextLine($value) { + return $value . $this->LE; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, ATTACHMENTS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + try { + if ( !@is_file($path) ) { + throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE); + } + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + echo $e->getMessage()."\n"; + if ( $e->getCode() == self::STOP_CRITICAL ) { + return false; + } + } + return true; + } + + /** + * Return the current array of attachments + * @return array + */ + public function GetAttachments() { + return $this->attachment; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access private + * @return string + */ + private function AttachAll() { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // Check for string attachment + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + if (in_array($attachment[0], $incl)) { continue; } + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + $incl[] = $attachment[0]; + if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; } + $cidUniq[$cid] = true; + + $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == 'inline') { + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + } + + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + + // Encode as string attachment + if($bString) { + $mime[] = $this->EncodeString($string, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } else { + $mime[] = $this->EncodeFile($path, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } + } + + $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE); + + return join('', $mime); + } + + /** + * Encodes attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @see EncodeFile() + * @access private + * @return string + */ + private function EncodeFile($path, $encoding = 'base64') { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); + } + if (function_exists('get_magic_quotes')) { + function get_magic_quotes() { + return false; + } + } + if (PHP_VERSION < 6) { + $magic_quotes = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->EncodeString($file_buffer, $encoding); + if (PHP_VERSION < 6) { set_magic_quotes_runtime($magic_quotes); } + return $file_buffer; + } catch (Exception $e) { + $this->SetError($e->getMessage()); + return ''; + } + } + + /** + * Encodes string to requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function EncodeString ($str, $encoding = 'base64') { + $encoded = ''; + switch(strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->FixEOL($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->EncodeQP($str); + break; + default: + $this->SetError($this->Lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string to best (shortest) of Q, B, quoted or none. + * @access public + * @return string + */ + public function EncodeHeader($str, $position = 'text') { + $x = 0; + + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know what value has magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + case 'comment': + $x = preg_match_all('/[()"]/', $str, $matches); + // Fall-through + case 'text': + default: + $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($x == 0) { + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if (strlen($str)/3 < $x) { + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->Base64EncodeWrapMB($str); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->EncodeQ($str, $position); + $encoded = $this->WrapText($encoded, $maxlen, true); + $encoded = str_replace('='.$this->LE, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Checks if a string contains multibyte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return bool + */ + public function HasMultiBytes($str) { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Correctly encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php + * @access public + * @param string $str multi-byte text to wrap encode + * @return string + */ + public function Base64EncodeWrapMB($str) { + $start = "=?".$this->CharSet."?B?"; + $end = "?="; + $encoded = ""; + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $offset = $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } + while (strlen($chunk) > $length); + + $encoded .= $chunk . $this->LE; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($this->LE)); + return $encoded; + } + + /** + * Encode string to quoted-printable. + * Only uses standard PHP, slow, but will always work + * @access public + * @param string $string the text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + */ + public function EncodeQPphp( $input = '', $line_max = 76, $space_conv = false) { + $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'); + $lines = preg_split('/(?:\r\n|\r|\n)/', $input); + $eol = "\r\n"; + $escape = '='; + $output = ''; + while( list(, $line) = each($lines) ) { + $linlen = strlen($line); + $newline = ''; + for($i = 0; $i < $linlen; $i++) { + $c = substr( $line, $i, 1 ); + $dec = ord( $c ); + if ( ( $i == 0 ) && ( $dec == 46 ) ) { // convert first point in the line into =2E + $c = '=2E'; + } + if ( $dec == 32 ) { + if ( $i == ( $linlen - 1 ) ) { // convert space at eol only + $c = '=20'; + } else if ( $space_conv ) { + $c = '=20'; + } + } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required + $h2 = floor($dec/16); + $h1 = floor($dec%16); + $c = $escape.$hex[$h2].$hex[$h1]; + } + if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted + $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay + $newline = ''; + // check if newline first character will be point or not + if ( $dec == 46 ) { + $c = '=2E'; + } + } + $newline .= $c; + } // end of for + $output .= $newline.$eol; + } // end of while + return $output; + } + + /** + * Encode string to RFC2045 (6.7) quoted-printable format + * Uses a PHP5 stream filter to do the encoding about 64x faster than the old version + * Also results in same content as you started with after decoding + * @see EncodeQPphp() + * @access public + * @param string $string the text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @param boolean $space_conv Dummy param for compatibility with existing EncodeQP function + * @return string + * @author Marcus Bointon + */ + public function EncodeQP($string, $line_max = 76, $space_conv = false) { + if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + return quoted_printable_encode($string); + } + $filters = stream_get_filters(); + if (!in_array('convert.*', $filters)) { //Got convert stream filter? + return $this->EncodeQPphp($string, $line_max, $space_conv); //Fall back to old implementation + } + $fp = fopen('php://temp/', 'r+'); + $string = preg_replace('/\r\n?/', $this->LE, $string); //Normalise line breaks + $params = array('line-length' => $line_max, 'line-break-chars' => $this->LE); + $s = stream_filter_append($fp, 'convert.quoted-printable-encode', STREAM_FILTER_READ, $params); + fputs($fp, $string); + rewind($fp); + $out = stream_get_contents($fp); + stream_filter_remove($s); + $out = preg_replace('/^\./m', '=2E', $out); //Encode . if it is first char on a line, workaround for bug in Exchange + fclose($fp); + return $out; + } + + /** + * Encode string to q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function EncodeQ ($str, $position = 'text') { + // There should not be any EOL in the string + $encoded = preg_replace('/[\r\n]*/', '', $str); + + switch (strtolower($position)) { + case 'phrase': + $encoded = preg_replace("/([^A-Za-z0-9!*+\/ -])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded); + break; + case 'comment': + $encoded = preg_replace("/([\(\)\"])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded); + case 'text': + default: + // Replace every high ascii, control =, ? and _ characters + //TODO using /e (equivalent to eval()) is probably not a good idea + $encoded = preg_replace('/([\000-\011\013\014\016-\037\075\077\137\177-\377])/e', + "'='.sprintf('%02X', ord('\\1'))", $encoded); + break; + } + + // Replace every spaces to _ (more readable than =20) + $encoded = str_replace(' ', '_', $encoded); + + return $encoded; + } + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + } + + /** + * Adds an embedded attachment. This can include images, sounds, and + * just about any other document. Make sure to set the $type to an + * image type. For JPEG images use "image/jpeg" and for GIF images + * use "image/gif". + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment. Use this to identify + * the Id for accessing the image in an HTML form. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + + if ( !@is_file($path) ) { + $this->SetError($this->Lang('file_access') . $path); + return false; + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + + return true; + } + + /** + * Returns true if an inline attachment is present. + * @access public + * @return bool + */ + public function InlineImageExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MESSAGE RESET + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @return void + */ + public function ClearAddresses() { + foreach($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @return void + */ + public function ClearCCs() { + foreach($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @return void + */ + public function ClearBCCs() { + foreach($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @return void + */ + public function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @return void + */ + public function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @return void + */ + public function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @return void + */ + public function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MISCELLANEOUS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * @access protected + * @return void + */ + protected function SetError($msg) { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { + $msg .= '

    ' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "

    \n"; + } + } + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function RFCDate() { + $tz = date('Z'); + $tzs = ($tz < 0) ? '-' : '+'; + $tz = abs($tz); + $tz = (int)($tz/3600)*100 + ($tz%3600)/60; + $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz); + + return $result; + } + + /** + * Returns the server hostname or 'localhost.localdomain' if unknown. + * @access private + * @return string + */ + private function ServerHostname() { + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } else { + $result = 'localhost.localdomain'; + } + + return $result; + } + + /** + * Returns a message in the appropriate language. + * @access private + * @return string + */ + private function Lang($key) { + if(count($this->language) < 1) { + $this->SetLanguage('en'); // set the default language + } + + if(isset($this->language[$key])) { + return $this->language[$key]; + } else { + return 'Language string failed to load: ' . $key; + } + } + + /** + * Returns true if an error occurred. + * @access public + * @return bool + */ + public function IsError() { + return ($this->error_count > 0); + } + + /** + * Changes every end of line from CR or LF to CRLF. + * @access private + * @return string + */ + private function FixEOL($str) { + $str = str_replace("\r\n", "\n", $str); + $str = str_replace("\r", "\n", $str); + $str = str_replace("\n", $this->LE, $str); + return $str; + } + + /** + * Adds a custom header. + * @access public + * @return void + */ + public function AddCustomHeader($custom_header) { + $this->CustomHeader[] = explode(':', $custom_header, 2); + } + + /** + * Evaluates the message and returns modifications for inline images and backgrounds + * @access public + * @return $message + */ + public function MsgHTML($message, $basedir = '') { + preg_match_all("/(src|background)=\"(.*)\"/Ui", $message, $images); + if(isset($images[2])) { + foreach($images[2] as $i => $url) { + // do not change urls for absolute images (thanks to corvuscorax) + if (!preg_match('#^[A-z]+://#',$url)) { + $filename = basename($url); + $directory = dirname($url); + ($directory == '.')?$directory='':''; + $cid = 'cid:' . md5($filename); + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $mimeType = self::_mime_types($ext); + if ( strlen($basedir) > 1 && substr($basedir,-1) != '/') { $basedir .= '/'; } + if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; } + if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64',$mimeType) ) { + $message = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $message); + } + } + } + } + $this->IsHTML(true); + $this->Body = $message; + $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s','',$message))); + if (!empty($textMsg) && empty($this->AltBody)) { + $this->AltBody = html_entity_decode($textMsg); + } + if (empty($this->AltBody)) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; + } + } + + /** + * Gets the MIME type of the embedded or inline image + * @param string File extension + * @access public + * @return string MIME type of ext + * @static + */ + public static function _mime_types($ext = '') { + $mimes = array( + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'doc' => 'application/msword', + 'bin' => 'application/macbinary', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xl' => 'application/excel', + 'eml' => 'message/rfc822' + ); + return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)]; + } + + /** + * Set (or reset) Class Objects (variables) + * + * Usage Example: + * $page->set('X-Priority', '3'); + * + * @access public + * @param string $name Parameter Name + * @param mixed $value Parameter Value + * NOTE: will not work with arrays, there are no arrays to set/reset + * @todo Should this not be using __set() magic function? + */ + public function set($name, $value = '') { + try { + if (isset($this->$name) ) { + $this->$name = $value; + } else { + throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL); + } + } catch (Exception $e) { + $this->SetError($e->getMessage()); + if ($e->getCode() == self::STOP_CRITICAL) { + return false; + } + } + return true; + } + + /** + * Strips newlines to prevent header injection. + * @access public + * @param string $str String + * @return string + */ + public function SecureHeader($str) { + $str = str_replace("\r", '', $str); + $str = str_replace("\n", '', $str); + return trim($str); + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $key_filename Parameter File Name + * @param string $key_pass Password for private key + */ + public function Sign($cert_filename, $key_filename, $key_pass) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $key_filename Parameter File Name + * @param string $key_pass Password for private key + */ + public function DKIM_QP($txt) { + $tmp=""; + $line=""; + for ($i=0;$iDKIM_private); + if ($this->DKIM_passphrase!='') { + $privKey = openssl_pkey_get_private($privKeyStr,$this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($s, $signature, $privKey)) { + return base64_encode($signature); + } + } + + /** + * Generate DKIM Canonicalization Header + * + * @access public + * @param string $s Header + */ + public function DKIM_HeaderC($s) { + $s=preg_replace("/\r\n\s+/"," ",$s); + $lines=explode("\r\n",$s); + foreach ($lines as $key=>$line) { + list($heading,$value)=explode(":",$line,2); + $heading=strtolower($heading); + $value=preg_replace("/\s+/"," ",$value) ; // Compress useless spaces + $lines[$key]=$heading.":".trim($value) ; // Don't forget to remove WSP around the value + } + $s=implode("\r\n",$lines); + return $s; + } + + /** + * Generate DKIM Canonicalization Body + * + * @access public + * @param string $body Message Body + */ + public function DKIM_BodyC($body) { + if ($body == '') return "\r\n"; + // stabilize line endings + $body=str_replace("\r\n","\n",$body); + $body=str_replace("\n","\r\n",$body); + // END stabilize line endings + while (substr($body,strlen($body)-4,4) == "\r\n\r\n") { + $body=substr($body,0,strlen($body)-2); + } + return $body; + } + + /** + * Create the DKIM header, body, as new header + * + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + */ + public function DKIM_Add($headers_line,$subject,$body) { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode("\r\n",$headers_line); + foreach($headers as $header) { + if (strpos($header,'From:') === 0) { + $from_header=$header; + } elseif (strpos($header,'To:') === 0) { + $to_header=$header; + } + } + $from = str_replace('|','=7C',$this->DKIM_QP($from_header)); + $to = str_replace('|','=7C',$this->DKIM_QP($to_header)); + $subject = str_replace('|','=7C',$this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body) ; // Length of body + $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body + $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";"; + $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n". + "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n". + "\th=From:To:Subject;\r\n". + "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n". + "\tz=$from\r\n". + "\t|$to\r\n". + "\t|$subject;\r\n". + "\tbh=" . $DKIMb64 . ";\r\n". + "\tb="; + $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); + $signed = $this->DKIM_Sign($toSign); + return "X-PHPMAILER-DKIM: phpmailer.worxware.com\r\n".$dkimhdrs.$signed."\r\n"; + } + + protected function doCallback($isSent,$to,$cc,$bcc,$subject,$body) { + if (!empty($this->action_function) && function_exists($this->action_function)) { + $params = array($isSent,$to,$cc,$bcc,$subject,$body); + call_user_func_array($this->action_function,$params); + } + } +} + +class phpmailerException extends Exception { + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
    \n"; + return $errorMsg; + } +} +?> \ No newline at end of file diff --git a/trunk/lib/snoopy/AUTHORS b/trunk/lib/snoopy/AUTHORS new file mode 100644 index 0000000000..dbbe3f4f94 --- /dev/null +++ b/trunk/lib/snoopy/AUTHORS @@ -0,0 +1,11 @@ +Monte Ohrt + - main Snoopy work + +Andrei Zmievski + - miscellaneous fixes + - read timeout support + - file submission capability + +Gene Wood + - bug fixes + - security fixes diff --git a/trunk/lib/snoopy/COPYING.lib b/trunk/lib/snoopy/COPYING.lib new file mode 100644 index 0000000000..3b204400cf --- /dev/null +++ b/trunk/lib/snoopy/COPYING.lib @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/trunk/lib/snoopy/ChangeLog b/trunk/lib/snoopy/ChangeLog new file mode 100644 index 0000000000..f4557613ed --- /dev/null +++ b/trunk/lib/snoopy/ChangeLog @@ -0,0 +1,105 @@ +Version 1.2.4 +------------- + + - fix command line escapement vulnerability with execution of curl binary on https fetches (mohrt) + +Version 1.2.3 +----------- + - updated the version variable in the code to reflect the new version number + - fixed a typo that I introduced in 1.2.2 (the first character of the file is a "z" (gene_wood, Marc Desrousseaux, Jan Pedersen) + - fixed BUG # 1328793 : fetch is case sensetive when it comes to the scheme (http / https) (gene_wood) + +Version 1.2.2 +----------- + - incorporated PATCH # 985470 : pass port information in http 1.1 Host header ( http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23 ) (gene_wood) + - fixed BUG # 1110049 : redirect is case sensitive + - fixed bug in security bugfix from 1.2.1 (gene_wood, kellan, zaruba) + +Version 1.2.1 +----------- + - fixed potential security issue with unchecked variables being passed to exec (for https with curl) (gene_wood) + - fixed BUG # 1086830 : submitlinks,fetchlinks and submittext expandlinks with the URI of the original page not the refreshed page (gene_wood) + - fixed BUG # 1077870 : Snoopy can't deal with multiple spaces in a refresh tag (gene_wood) + - fixed BUG # 864047 : Root relative links are treated as relative (gene_wood) + - fixed BUG # 1097134 : Undefined URI_PARTS["path"] generates Notice (gene_wood) + +Version 1.2 +----------- + - fixed BUG # 1014823 : Meta redirect regex inaccurate (gene_wood) + - fixed BUG # 999079 : Trailing slashes not removed in uri passed to fetchlinks (gene_wood) + - fixed BUG # 642958 and 912060 : $URI_PARTS["query"] causing undefined variable notices (gene_wood) + - fixed BUG # 626849 : cURL security risk (Tajh Leitso, gene_wood) + - fixed BUG # 626849 : Corrects the redirect function under the submit functions (Tajh Leitso, gene_wood) + - fixed BUG # 912060 : Undefined variable: postdata (gene_wood) + - fixed BUG # 858526 : win32 tmp/$headerfile create error (gene_wood) + - fixed BUG # 929682 : Called undefined function is_executable() on line 194. (gene_wood) + - fixed BUG # 859711 : typo: http://snoopy.sourceforge.com (gene_wood) + - fixed BUG # 852993 : double urlencoding breaks redirect (gene_wood) + - added proxy user/pass support (Robert Zwink, Monte) + - fixed post data array problem (stefan, Monte) + +Version 1.01 +----------- + - fixed problem with PHP 4.3.2 and fread() (Monte) + +Version 1.0 +----------- + - added textarea to stripform functionality (Monte) + - fixed multiple cookie setting problem (Monte) + - fixed problem where extra text inside curl_path variable to the location of your curl installation. Here's an example of the code : + include "Snoopy.class.php"; + $snoopy = new Snoopy; + $snoopy->curl_path="/usr/bin/curl"; + +Q: where does the function preg_match_all come from? +A: PCRE functions in PHP 3.0.9 and later + +Q: I get the error: Warning: Wrong parameter count for fsockopen() +A: Upgrade your verion of PHP to 3.0.9 or later + +Q: Snoopy cuts of my results every time. What's wrong? +A: Upgrade your verion of PHP to 3.0.9 or later diff --git a/trunk/lib/snoopy/INSTALL b/trunk/lib/snoopy/INSTALL new file mode 100644 index 0000000000..b6315ef78a --- /dev/null +++ b/trunk/lib/snoopy/INSTALL @@ -0,0 +1,2 @@ +Put Snoopy.class.php into one of the directories specified in your +php.ini include_path directive. diff --git a/trunk/lib/snoopy/NEWS b/trunk/lib/snoopy/NEWS new file mode 100644 index 0000000000..a2ae3d9bde --- /dev/null +++ b/trunk/lib/snoopy/NEWS @@ -0,0 +1,61 @@ +RELEASE NOTE: v1.2.4 +October 22, 2008 + +https fetches were not properly escaping shell args for curl binary execution. This is fixed. + +RELEASE NOTE: v1.2.3 +November 7, 2005 + +A typo was introduced in 1.2.2 which broke the whole release. This has been fixed. +A couple small fixes have been implemented also. + +RELEASE NOTE: v1.2.2 +October 30, 2005 + +Fixed a bug with the bugfix for the security hole. + +RELEASE NOTE: v1.2.1 +October 24, 2005 + +Fixed a few outstanding bugs and a potential security hole. + +RELEASE NOTE: v1.2 +November 17, 2004 + +Fixed a number of outstanding bugs. + +RELEASE NOTE: v1.01 + +PHP fixed a bug with fread() which consequently broke the way Snoopy called it. This has been fixed. +Renamed Snoopy.class.inc to Snoopy.class.php for proper file extention. + +RELEASE NOTE: v1.0 + +Added fetchform() function for fetching form elements from an html page. +For SSL support, you must have cURL installed. see http://curl.haxx.se +for details. Snoopy does not use the cURL library fuctions within PHP, +as these are not stable as of this Snoopy release. +Fixed bug with posting arrays of data. +Added status variable to track http status. +Several other bug fixes, see Changelog. +RELEASE NOTE: v0.93 + +A bug was fixed with redirection headers not containing the hostname, doubling up the redirection location URL. + +There is also a new variable, $lastredirectaddr that contains the last redirection URL. + +RELEASE NOTE: v0.92 +March 9, 2000 + +A bug was fixed with redirection on MS web servers. Also, cookies are now passed through redirects. + +This release also adds the ability to traverse html framed pages. Just set $maxframes to the recursion depth you want to allow, and results are returned in $this->results as an array. See the README for an example. + +-Monte + +RELEASE NOTE: v0.91 +February 22, 2000 + +In previous versions of Snoopy, $this->header was an array containing key/value pairs of headers returned from fetched content, not including HTTP and GET headers. If a key value was the same, the old value was overwritten (Two Set-Cookie: headers for example). This was overcome by making $this->header a simple array containing every header returned. Therefore, it will now be up to the programmer to split these headers into key/value pairs if so desired. + +-Monte diff --git a/trunk/lib/snoopy/README b/trunk/lib/snoopy/README new file mode 100644 index 0000000000..f6664b4131 --- /dev/null +++ b/trunk/lib/snoopy/README @@ -0,0 +1,262 @@ +NAME: + + Snoopy - the PHP net client v1.2.4 + +SYNOPSIS: + + include "Snoopy.class.php"; + $snoopy = new Snoopy; + + $snoopy->fetchtext("http://www.php.net/"); + print $snoopy->results; + + $snoopy->fetchlinks("http://www.phpbuilder.com/"); + print $snoopy->results; + + $submit_url = "http://lnk.ispi.net/texis/scripts/msearch/netsearch.html"; + + $submit_vars["q"] = "amiga"; + $submit_vars["submit"] = "Search!"; + $submit_vars["searchhost"] = "Altavista"; + + $snoopy->submit($submit_url,$submit_vars); + print $snoopy->results; + + $snoopy->maxframes=5; + $snoopy->fetch("http://www.ispi.net/"); + echo "
    \n";
    +	echo htmlentities($snoopy->results[0]); 
    +	echo htmlentities($snoopy->results[1]); 
    +	echo htmlentities($snoopy->results[2]); 
    +	echo "
    \n"; + + $snoopy->fetchform("http://www.altavista.com"); + print $snoopy->results; + +DESCRIPTION: + + What is Snoopy? + + Snoopy is a PHP class that simulates a web browser. It automates the + task of retrieving web page content and posting forms, for example. + + Some of Snoopy's features: + + * easily fetch the contents of a web page + * easily fetch the text from a web page (strip html tags) + * easily fetch the the links from a web page + * supports proxy hosts + * supports basic user/pass authentication + * supports setting user_agent, referer, cookies and header content + * supports browser redirects, and controlled depth of redirects + * expands fetched links to fully qualified URLs (default) + * easily submit form data and retrieve the results + * supports following html frames (added v0.92) + * supports passing cookies on redirects (added v0.92) + + +REQUIREMENTS: + + Snoopy requires PHP with PCRE (Perl Compatible Regular Expressions), + which should be PHP 3.0.9 and up. For read timeout support, it requires + PHP 4 Beta 4 or later. Snoopy was developed and tested with PHP 3.0.12. + +CLASS METHODS: + + fetch($URI) + ----------- + + This is the method used for fetching the contents of a web page. + $URI is the fully qualified URL of the page to fetch. + The results of the fetch are stored in $this->results. + If you are fetching frames, then $this->results + contains each frame fetched in an array. + + fetchtext($URI) + --------------- + + This behaves exactly like fetch() except that it only returns + the text from the page, stripping out html tags and other + irrelevant data. + + fetchform($URI) + --------------- + + This behaves exactly like fetch() except that it only returns + the form elements from the page, stripping out html tags and other + irrelevant data. + + fetchlinks($URI) + ---------------- + + This behaves exactly like fetch() except that it only returns + the links from the page. By default, relative links are + converted to their fully qualified URL form. + + submit($URI,$formvars) + ---------------------- + + This submits a form to the specified $URI. $formvars is an + array of the form variables to pass. + + + submittext($URI,$formvars) + -------------------------- + + This behaves exactly like submit() except that it only returns + the text from the page, stripping out html tags and other + irrelevant data. + + submitlinks($URI) + ---------------- + + This behaves exactly like submit() except that it only returns + the links from the page. By default, relative links are + converted to their fully qualified URL form. + + +CLASS VARIABLES: (default value in parenthesis) + + $host the host to connect to + $port the port to connect to + $proxy_host the proxy host to use, if any + $proxy_port the proxy port to use, if any + $agent the user agent to masqerade as (Snoopy v0.1) + $referer referer information to pass, if any + $cookies cookies to pass if any + $rawheaders other header info to pass, if any + $maxredirs maximum redirects to allow. 0=none allowed. (5) + $offsiteok whether or not to allow redirects off-site. (true) + $expandlinks whether or not to expand links to fully qualified URLs (true) + $user authentication username, if any + $pass authentication password, if any + $accept http accept types (image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*) + $error where errors are sent, if any + $response_code responde code returned from server + $headers headers returned from server + $maxlength max return data length + $read_timeout timeout on read operations (requires PHP 4 Beta 4+) + set to 0 to disallow timeouts + $timed_out true if a read operation timed out (requires PHP 4 Beta 4+) + $maxframes number of frames we will follow + $status http status of fetch + $temp_dir temp directory that the webserver can write to. (/tmp) + $curl_path system path to cURL binary, set to false if none + + +EXAMPLES: + + Example: fetch a web page and display the return headers and + the contents of the page (html-escaped): + + include "Snoopy.class.php"; + $snoopy = new Snoopy; + + $snoopy->user = "joe"; + $snoopy->pass = "bloe"; + + if($snoopy->fetch("http://www.slashdot.org/")) + { + echo "response code: ".$snoopy->response_code."
    \n"; + while(list($key,$val) = each($snoopy->headers)) + echo $key.": ".$val."
    \n"; + echo "

    \n"; + + echo "

    ".htmlspecialchars($snoopy->results)."
    \n"; + } + else + echo "error fetching document: ".$snoopy->error."\n"; + + + + Example: submit a form and print out the result headers + and html-escaped page: + + include "Snoopy.class.php"; + $snoopy = new Snoopy; + + $submit_url = "http://lnk.ispi.net/texis/scripts/msearch/netsearch.html"; + + $submit_vars["q"] = "amiga"; + $submit_vars["submit"] = "Search!"; + $submit_vars["searchhost"] = "Altavista"; + + + if($snoopy->submit($submit_url,$submit_vars)) + { + while(list($key,$val) = each($snoopy->headers)) + echo $key.": ".$val."
    \n"; + echo "

    \n"; + + echo "

    ".htmlspecialchars($snoopy->results)."
    \n"; + } + else + echo "error fetching document: ".$snoopy->error."\n"; + + + + Example: showing functionality of all the variables: + + + include "Snoopy.class.php"; + $snoopy = new Snoopy; + + $snoopy->proxy_host = "my.proxy.host"; + $snoopy->proxy_port = "8080"; + + $snoopy->agent = "(compatible; MSIE 4.01; MSN 2.5; AOL 4.0; Windows 98)"; + $snoopy->referer = "http://www.microsnot.com/"; + + $snoopy->cookies["SessionID"] = 238472834723489l; + $snoopy->cookies["favoriteColor"] = "RED"; + + $snoopy->rawheaders["Pragma"] = "no-cache"; + + $snoopy->maxredirs = 2; + $snoopy->offsiteok = false; + $snoopy->expandlinks = false; + + $snoopy->user = "joe"; + $snoopy->pass = "bloe"; + + if($snoopy->fetchtext("http://www.phpbuilder.com")) + { + while(list($key,$val) = each($snoopy->headers)) + echo $key.": ".$val."
    \n"; + echo "

    \n"; + + echo "

    ".htmlspecialchars($snoopy->results)."
    \n"; + } + else + echo "error fetching document: ".$snoopy->error."\n"; + + + Example: fetched framed content and display the results + + include "Snoopy.class.php"; + $snoopy = new Snoopy; + + $snoopy->maxframes = 5; + + if($snoopy->fetch("http://www.ispi.net/")) + { + echo "
    ".htmlspecialchars($snoopy->results[0])."
    \n"; + echo "
    ".htmlspecialchars($snoopy->results[1])."
    \n"; + echo "
    ".htmlspecialchars($snoopy->results[2])."
    \n"; + } + else + echo "error fetching document: ".$snoopy->error."\n"; + + +COPYRIGHT: + Copyright(c) 1999,2000 ispi. All rights reserved. + This software is released under the GNU General Public License. + Please read the disclaimer at the top of the Snoopy.class.php file. + + +THANKS: + Special Thanks to: + Peter Sorger help fixing a redirect bug + Andrei Zmievski implementing time out functionality + Patric Sandelin help with fetchform debugging + Carmelo misc bug fixes with frames diff --git a/trunk/lib/snoopy/TODO b/trunk/lib/snoopy/TODO new file mode 100644 index 0000000000..d46738c162 --- /dev/null +++ b/trunk/lib/snoopy/TODO @@ -0,0 +1,9 @@ +* fetch other types of protocols such as ftp, nntp, gopher, etc. +* post forms with http file upload (I didn't have this need, + but it should be fairly straightforward) +* expand links, image tags, and form actions to fully + qualified URLs + +Bugs +---- +* none known diff --git a/trunk/lib/snoopy/snoopy.class.php b/trunk/lib/snoopy/snoopy.class.php new file mode 100644 index 0000000000..531161059d --- /dev/null +++ b/trunk/lib/snoopy/snoopy.class.php @@ -0,0 +1,1250 @@ + +Copyright (c): 1999-2008 New Digital Group, all rights reserved +Version: 1.2.4 + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You may contact the author of Snoopy by e-mail at: +monte@ohrt.com + +The latest version of Snoopy can be obtained from: +http://snoopy.sourceforge.net/ + +*************************************************/ + +class Snoopy +{ + /**** Public variables ****/ + + /* user definable vars */ + + var $host = "www.php.net"; // host name we are connecting to + var $port = 80; // port we are connecting to + var $proxy_host = ""; // proxy host to use + var $proxy_port = ""; // proxy port to use + var $proxy_user = ""; // proxy user to use + var $proxy_pass = ""; // proxy password to use + + var $agent = "Snoopy v1.2.4"; // agent we masquerade as + var $referer = ""; // referer info to pass + var $cookies = array(); // array of cookies to pass + // $cookies["username"]="joe"; + var $rawheaders = array(); // array of raw headers to send + // $rawheaders["Content-type"]="text/html"; + + var $maxredirs = 5; // http redirection depth maximum. 0 = disallow + var $lastredirectaddr = ""; // contains address of last redirected address + var $offsiteok = true; // allows redirection off-site + var $maxframes = 0; // frame content depth maximum. 0 = disallow + var $expandlinks = true; // expand links to fully qualified URLs. + // this only applies to fetchlinks() + // submitlinks(), and submittext() + var $passcookies = true; // pass set cookies back through redirects + // NOTE: this currently does not respect + // dates, domains or paths. + + var $user = ""; // user for http authentication + var $pass = ""; // password for http authentication + + // http accept types + var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + + var $results = ""; // where the content is put + + var $error = ""; // error messages sent here + var $response_code = ""; // response code returned from server + var $headers = array(); // headers returned from server sent here + var $maxlength = 500000; // max return data length (body) + var $read_timeout = 0; // timeout on read operations, in seconds + // supported only since PHP 4 Beta 4 + // set to 0 to disallow timeouts + var $timed_out = false; // if a read operation timed out + var $status = 0; // http request status + + var $temp_dir = "/tmp"; // temporary directory that the webserver + // has permission to write to. + // under Windows, this should be C:\temp + + var $curl_path = "/usr/local/bin/curl"; + // Snoopy will use cURL for fetching + // SSL content if a full system path to + // the cURL binary is supplied here. + // set to false if you do not have + // cURL installed. See http://curl.haxx.se + // for details on installing cURL. + // Snoopy does *not* use the cURL + // library functions built into php, + // as these functions are not stable + // as of this Snoopy release. + + /**** Private variables ****/ + + var $_maxlinelen = 4096; // max line length (headers) + + var $_httpmethod = "GET"; // default http request method + var $_httpversion = "HTTP/1.0"; // default http request version + var $_submit_method = "POST"; // default submit method + var $_submit_type = "application/x-www-form-urlencoded"; // default submit type + var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type + var $_redirectaddr = false; // will be set if page fetched is a redirect + var $_redirectdepth = 0; // increments on an http redirect + var $_frameurls = array(); // frame src urls + var $_framedepth = 0; // increments on frame depth + + var $_isproxy = false; // set if using a proxy server + var $_fp_timeout = 30; // timeout for socket connection + +/*======================================================================*\ + Function: fetch + Purpose: fetch the contents of a web page + (and possibly other protocols in the + future like ftp, nntp, gopher, etc.) + Input: $URI the location of the page to fetch + Output: $this->results the output text from the fetch +\*======================================================================*/ + + function fetch($URI) + { + + //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS); + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + if (empty($URI_PARTS["path"])) + $URI_PARTS["path"] = ''; + + switch(strtolower($URI_PARTS["scheme"])) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_httpmethod); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_httpmethod); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: submit + Purpose: submit an http form + Input: $URI the location to post the data + $formvars the formvars to use. + format: $formvars["var"] = "val"; + $formfiles an array of files to submit + format: $formfiles["var"] = "/dir/filename.ext"; + Output: $this->results the text output from the post +\*======================================================================*/ + + function submit($URI, $formvars="", $formfiles="") + { + unset($postdata); + + $postdata = $this->_prepare_post_body($formvars, $formfiles); + + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + if (empty($URI_PARTS["path"])) + $URI_PARTS["path"] = ''; + + switch(strtolower($URI_PARTS["scheme"])) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_submit_method,$this->_submit_type,$postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: fetchlinks + Purpose: fetch the links from a web page + Input: $URI where you are fetching from + Output: $this->results an array of the URLs +\*======================================================================*/ + + function fetchlinks($URI) + { + if ($this->fetch($URI)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_striplinks($this->results[$x]); + } + else + $this->results = $this->_striplinks($this->results); + + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results, $URI); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: fetchform + Purpose: fetch the form elements from a web page + Input: $URI where you are fetching from + Output: $this->results the resulting html form +\*======================================================================*/ + + function fetchform($URI) + { + + if ($this->fetch($URI)) + { + + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_stripform($this->results[$x]); + } + else + $this->results = $this->_stripform($this->results); + + return true; + } + else + return false; + } + + +/*======================================================================*\ + Function: fetchtext + Purpose: fetch the text from a web page, stripping the links + Input: $URI where you are fetching from + Output: $this->results the text from the web page +\*======================================================================*/ + + function fetchtext($URI) + { + if($this->fetch($URI)) + { + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_striptext($this->results[$x]); + } + else + $this->results = $this->_striptext($this->results); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submitlinks + Purpose: grab links from a form submission + Input: $URI where you are submitting from + Output: $this->results an array of the links from the post +\*======================================================================*/ + + function submitlinks($URI, $formvars="", $formfiles="") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + { + $this->results[$x] = $this->_striplinks($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striplinks($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submittext + Purpose: grab text from a form submission + Input: $URI where you are submitting from + Output: $this->results the text from the web page +\*======================================================================*/ + + function submittext($URI, $formvars = "", $formfiles = "") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + { + $this->results[$x] = $this->_striptext($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striptext($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + + + +/*======================================================================*\ + Function: set_submit_multipart + Purpose: Set the form submission content type to + multipart/form-data +\*======================================================================*/ + function set_submit_multipart() + { + $this->_submit_type = "multipart/form-data"; + } + + +/*======================================================================*\ + Function: set_submit_normal + Purpose: Set the form submission content type to + application/x-www-form-urlencoded +\*======================================================================*/ + function set_submit_normal() + { + $this->_submit_type = "application/x-www-form-urlencoded"; + } + + + + +/*======================================================================*\ + Private functions +\*======================================================================*/ + + +/*======================================================================*\ + Function: _striplinks + Purpose: strip the hyperlinks from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _striplinks($document) + { + preg_match_all("'<\s*a\s.*?href\s*=\s* # find ]+)) # if quote found, match up to next matching + # quote, otherwise match up to next space + 'isx",$document,$links); + + + // catenate the non-empty matches from the conditional subpattern + + while(list($key,$val) = each($links[2])) + { + if(!empty($val)) + $match[] = $val; + } + + while(list($key,$val) = each($links[3])) + { + if(!empty($val)) + $match[] = $val; + } + + // return the links + return $match; + } + +/*======================================================================*\ + Function: _stripform + Purpose: strip the form elements from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _stripform($document) + { + preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements); + + // catenate the matches + $match = implode("\r\n",$elements[0]); + + // return the links + return $match; + } + + + +/*======================================================================*\ + Function: _striptext + Purpose: strip the text from an html document + Input: $document document to strip. + Output: $text the resulting text +\*======================================================================*/ + + function _striptext($document) + { + + // I didn't use preg eval (//e) since that is only available in PHP 4.0. + // so, list your entities one by one here. I included some of the + // more common ones. + + $search = array("']*?>.*?'si", // strip out javascript + "'<[\/\!]*?[^<>]*?>'si", // strip out html tags + "'([\r\n])[\s]+'", // strip out white space + "'&(quot|#34|#034|#x22);'i", // replace html entities + "'&(amp|#38|#038|#x26);'i", // added hexadecimal values + "'&(lt|#60|#060|#x3c);'i", + "'&(gt|#62|#062|#x3e);'i", + "'&(nbsp|#160|#xa0);'i", + "'&(iexcl|#161);'i", + "'&(cent|#162);'i", + "'&(pound|#163);'i", + "'&(copy|#169);'i", + "'&(reg|#174);'i", + "'&(deg|#176);'i", + "'&(#39|#039|#x27);'", + "'&(euro|#8364);'i", // europe + "'&a(uml|UML);'", // german + "'&o(uml|UML);'", + "'&u(uml|UML);'", + "'&A(uml|UML);'", + "'&O(uml|UML);'", + "'&U(uml|UML);'", + "'ß'i", + ); + $replace = array( "", + "", + "\\1", + "\"", + "&", + "<", + ">", + " ", + chr(161), + chr(162), + chr(163), + chr(169), + chr(174), + chr(176), + chr(39), + chr(128), + "", + "", + "", + "", + "", + "", + "", + ); + + $text = preg_replace($search,$replace,$document); + + return $text; + } + +/*======================================================================*\ + Function: _expandlinks + Purpose: expand each link into a fully qualified URL + Input: $links the links to qualify + $URI the full URI to get the base from + Output: $expandedLinks the expanded links +\*======================================================================*/ + + function _expandlinks($links,$URI) + { + + preg_match("/^[^\?]+/",$URI,$match); + + $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]); + $match = preg_replace("|/$|","",$match); + $match_part = parse_url($match); + $match_root = + $match_part["scheme"]."://".$match_part["host"]; + + $search = array( "|^http://".preg_quote($this->host)."|i", + "|^(\/)|i", + "|^(?!http://)(?!mailto:)|i", + "|/\./|", + "|/[^\/]+/\.\./|" + ); + + $replace = array( "", + $match_root."/", + $match."/", + "/", + "/" + ); + + $expandedLinks = preg_replace($search,$replace,$links); + + return $expandedLinks; + } + +/*======================================================================*\ + Function: _httprequest + Purpose: go get the http data from the server + Input: $url the url to fetch + $fp the current open file pointer + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="") + { + $cookie_headers = ''; + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + $headers = $http_method." ".$url." ".$this->_httpversion."\r\n"; + if(!empty($this->agent)) + $headers .= "User-Agent: ".$this->agent."\r\n"; + if(!empty($this->host) && !isset($this->rawheaders['Host'])) { + $headers .= "Host: ".$this->host; + if(!empty($this->port)) + $headers .= ":".$this->port; + $headers .= "\r\n"; + } + if(!empty($this->accept)) + $headers .= "Accept: ".$this->accept."\r\n"; + if(!empty($this->referer)) + $headers .= "Referer: ".$this->referer."\r\n"; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_headers .= 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers .= substr($cookie_headers,0,-2) . "\r\n"; + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers .= $headerKey.": ".$headerVal."\r\n"; + } + if(!empty($content_type)) { + $headers .= "Content-type: $content_type"; + if ($content_type == "multipart/form-data") + $headers .= "; boundary=".$this->_mime_boundary; + $headers .= "\r\n"; + } + if(!empty($body)) + $headers .= "Content-length: ".strlen($body)."\r\n"; + if(!empty($this->user) || !empty($this->pass)) + $headers .= "Authorization: Basic ".base64_encode($this->user.":".$this->pass)."\r\n"; + + //add proxy auth headers + if(!empty($this->proxy_user)) + $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass)."\r\n"; + + + $headers .= "\r\n"; + + // set the read timeout if needed + if ($this->read_timeout > 0) + socket_set_timeout($fp, $this->read_timeout); + $this->timed_out = false; + + fwrite($fp,$headers.$body,strlen($headers.$body)); + + $this->_redirectaddr = false; + unset($this->headers); + + while($currentHeader = fgets($fp,$this->_maxlinelen)) + { + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + if($currentHeader == "\r\n") + break; + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location:|URI:)/i",$currentHeader)) + { + // get URL portion of the redirect + preg_match("/^(Location:|URI:)[ ]+(.*)/i",chop($currentHeader),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$currentHeader)) + { + if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status)) + { + $this->status= $status[1]; + } + $this->response_code = $currentHeader; + } + + $this->headers[] = $currentHeader; + } + + $results = ''; + do { + $_data = fread($fp, $this->maxlength); + if (strlen($_data) == 0) { + break; + } + $results .= $_data; + } while(true); + + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + // check if there is a a redirect meta tag + + if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + return true; + } + +/*======================================================================*\ + Function: _httpsrequest + Purpose: go get the https data from the server using curl + Input: $url the url to fetch + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httpsrequest($url,$URI,$http_method,$content_type="",$body="") + { + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $headers = array(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + // GET ... header not needed for curl + //$headers[] = $http_method." ".$url." ".$this->_httpversion; + if(!empty($this->agent)) + $headers[] = "User-Agent: ".$this->agent; + if(!empty($this->host)) + if(!empty($this->port)) + $headers[] = "Host: ".$this->host.":".$this->port; + else + $headers[] = "Host: ".$this->host; + if(!empty($this->accept)) + $headers[] = "Accept: ".$this->accept; + if(!empty($this->referer)) + $headers[] = "Referer: ".$this->referer; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_str = 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers[] = substr($cookie_str,0,-2); + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers[] = $headerKey.": ".$headerVal; + } + if(!empty($content_type)) { + if ($content_type == "multipart/form-data") + $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary; + else + $headers[] = "Content-type: $content_type"; + } + if(!empty($body)) + $headers[] = "Content-length: ".strlen($body); + if(!empty($this->user) || !empty($this->pass)) + $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass); + + for($curr_header = 0; $curr_header < count($headers); $curr_header++) { + $safer_header = strtr( $headers[$curr_header], "\"", " " ); + $cmdline_params .= " -H \"".$safer_header."\""; + } + + if(!empty($body)) + $cmdline_params .= " -d \"$body\""; + + if($this->read_timeout > 0) + $cmdline_params .= " -m ".$this->read_timeout; + + $headerfile = tempnam($temp_dir, "sno"); + + exec($this->curl_path." -k -D \"$headerfile\"".$cmdline_params." \"".escapeshellcmd($URI)."\"",$results,$return); + + if($return) + { + $this->error = "Error: cURL could not retrieve the document, error $return."; + return false; + } + + + $results = implode("\r\n",$results); + + $result_headers = file("$headerfile"); + + $this->_redirectaddr = false; + unset($this->headers); + + for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++) + { + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader])) + { + // get URL portion of the redirect + preg_match("/^(Location: |URI:)\s+(.*)/",chop($result_headers[$currentHeader]),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$result_headers[$currentHeader])) + $this->response_code = $result_headers[$currentHeader]; + + $this->headers[] = $result_headers[$currentHeader]; + } + + // check if there is a a redirect meta tag + + if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + unlink("$headerfile"); + + return true; + } + +/*======================================================================*\ + Function: setcookies() + Purpose: set cookies for a redirection +\*======================================================================*/ + + function setcookies() + { + for($x=0; $xheaders); $x++) + { + if(preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x],$match)) + $this->cookies[$match[1]] = urldecode($match[2]); + } + } + + +/*======================================================================*\ + Function: _check_timeout + Purpose: checks whether timeout has occurred + Input: $fp file pointer +\*======================================================================*/ + + function _check_timeout($fp) + { + if ($this->read_timeout > 0) { + $fp_status = socket_get_status($fp); + if ($fp_status["timed_out"]) { + $this->timed_out = true; + return true; + } + } + return false; + } + +/*======================================================================*\ + Function: _connect + Purpose: make a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _connect(&$fp) + { + if(!empty($this->proxy_host) && !empty($this->proxy_port)) + { + $this->_isproxy = true; + + $host = $this->proxy_host; + $port = $this->proxy_port; + } + else + { + $host = $this->host; + $port = $this->port; + } + + $this->status = 0; + + if($fp = fsockopen( + $host, + $port, + $errno, + $errstr, + $this->_fp_timeout + )) + { + // socket connection succeeded + + return true; + } + else + { + // socket connection failed + $this->status = $errno; + switch($errno) + { + case -3: + $this->error="socket creation failed (-3)"; + case -4: + $this->error="dns lookup failure (-4)"; + case -5: + $this->error="connection refused or timed out (-5)"; + default: + $this->error="connection failed (".$errno.")"; + } + return false; + } + } +/*======================================================================*\ + Function: _disconnect + Purpose: disconnect a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _disconnect($fp) + { + return(fclose($fp)); + } + + +/*======================================================================*\ + Function: _prepare_post_body + Purpose: Prepare post body according to encoding type + Input: $formvars - form variables + $formfiles - form upload files + Output: post body +\*======================================================================*/ + + function _prepare_post_body($formvars, $formfiles) + { + settype($formvars, "array"); + settype($formfiles, "array"); + $postdata = ''; + + if (count($formvars) == 0 && count($formfiles) == 0) + return; + + switch ($this->_submit_type) { + case "application/x-www-form-urlencoded": + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&"; + } + } else + $postdata .= urlencode($key)."=".urlencode($val)."&"; + } + break; + + case "multipart/form-data": + $this->_mime_boundary = "Snoopy".md5(uniqid(microtime())); + + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n"; + $postdata .= "$cur_val\r\n"; + } + } else { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"; + $postdata .= "$val\r\n"; + } + } + + reset($formfiles); + while (list($field_name, $file_names) = each($formfiles)) { + settype($file_names, "array"); + while (list(, $file_name) = each($file_names)) { + if (!is_readable($file_name)) continue; + + $fp = fopen($file_name, "r"); + $file_content = fread($fp, filesize($file_name)); + fclose($fp); + $base_name = basename($file_name); + + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n"; + $postdata .= "$file_content\r\n"; + } + } + $postdata .= "--".$this->_mime_boundary."--\r\n"; + break; + } + + return $postdata; + } +} + +?> diff --git a/trunk/lib/spyc/COPYING b/trunk/lib/spyc/COPYING new file mode 100644 index 0000000000..8e7ddbcf6b --- /dev/null +++ b/trunk/lib/spyc/COPYING @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 Vladimir Andersen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/trunk/lib/spyc/spyc.class.php b/trunk/lib/spyc/spyc.class.php new file mode 100644 index 0000000000..e19d562035 --- /dev/null +++ b/trunk/lib/spyc/spyc.class.php @@ -0,0 +1,1046 @@ + + * @author Chris Wanstrath + * @link http://code.google.com/p/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * + * or: + * + * $array = Spyc::YAMLLoad($file); + * + * or: + * + * $array = spyc_load_file($file); + * + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + public $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_use_syck_is_possible = false; + + + + /**#@+ + * @access private + * @var mixed + */ + private $_dumpIndent; + private $_dumpWordWrap; + private $_containsGroupAnchor = false; + private $_containsGroupAlias = false; + private $path; + private $result; + private $LiteralPlaceHolder = '___YAML_Literal_Block___'; + private $SavedGroups = array(); + private $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + private $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + public $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + public function load ($input) { + return $this->__loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + public function loadFile ($file) { + return $this->__load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + public static function YAMLLoad($input) { + $Spyc = new Spyc; + return $Spyc->__load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * + * @access public + * @return array + * @param string $input String containing YAML + */ + public static function YAMLLoadString($input) { + $Spyc = new Spyc; + return $Spyc->__loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public static function YAMLDump($array,$indent = false,$wordwrap = false) { + $spyc = new Spyc; + return $spyc->dump($array,$indent,$wordwrap); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public function dump($array,$indent = false,$wordwrap = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if (in_array ($value, array ('true', 'TRUE', 'false', 'FALSE', 'y', 'Y', 'n', 'N', 'null', 'NULL'), true)) { + $value = $this->_doLiteralBlock($value,$indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = ($value) ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . self::REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) + $value = '"' . $value . '"'; + } + + + return $value; + } + +// LOADING FUNCTIONS + + private function __load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + private function __loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + private function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ('', $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = self::stripIndent($line, $this->indent); + if (self::isComment($line)) continue; + if (self::isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = self::startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= $this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + while (++$i < $cnt && self::greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + + + if (strpos ($line, '#')) { + if (strpos ($line, '"') === false && strpos ($line, "'") === false) + $line = preg_replace('/\s+#(.+)$/','',$line); + } + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + private function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + return file($input); + + return $this->loadFromString($input); + } + + private function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + private function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) + return $this->returnMappedSequence($line); + + if ($this->startsMappedValue($line)) + return $this->returnMappedValue($line); + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + private function _toType($value) { + if ($value === '') return null; + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\'')); + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if (!$is_quoted) $value = str_replace('\n', "\n", $value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if (in_array($value, + array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) { + return true; + } + + if (in_array(strtolower($value), + array('false', 'off', '-', 'no', 'n'))) { + return false; + } + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + private function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(', ',$inline); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + return $explode; + } + + private function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + private function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + private function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + private function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + private static function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + private static function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = self::stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = self::stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + private static function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + private function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + private function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + private static function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + private static function isEmpty ($line) { + return (trim ($line) === ''); + } + + + private function isArrayElement ($line) { + if (!$line) return false; + if ($line[0] != '-') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + private function isHashElement ($line) { + return strpos($line, ':'); + } + + private function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + private static function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + private function startsMappedSequence ($line) { + return ($line[0] == '-' && substr ($line, -1, 1) == ':'); + } + + private function returnMappedSequence ($line) { + $array = array(); + $key = self::unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + private function returnMappedValue ($line) { + $array = array(); + $key = self::unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + private function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + private function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + private function returnPlainArray ($line) { + return $this->_toType($line); + } + + private function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ':')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(':',$line); + $key = trim($explode[0]); + array_shift($explode); + $value = trim(implode(':',$explode)); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + private function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + $array[] = $value; + return $array; + } + + + private function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + private function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + private function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} + +// Enable use of Spyc from command line +// The syntax is the following: php spyc.php spyc.yaml + +define ('SPYC_FROM_COMMAND_LINE', false); + +do { + if (!SPYC_FROM_COMMAND_LINE) break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || $_SERVER['PHP_SELF'] != 'spyc.php') break; + $file = $argv[1]; + printf ("Spyc loading file: %s\n", $file); + print_r (spyc_load_file ($file)); +} while (0); \ No newline at end of file diff --git a/trunk/lib/ubb/ubb.class.php b/trunk/lib/ubb/ubb.class.php new file mode 100644 index 0000000000..3114f64599 --- /dev/null +++ b/trunk/lib/ubb/ubb.class.php @@ -0,0 +1,158 @@ + + * @site http://xheditor.com/ + * @licence LGPL(http://www.opensource.org/licenses/lgpl-license.php) + * + * @Version: 0.9.8 (build 100505) + */ +class ubb +{ + public static function parseUBB($sUBB) + { + $sHtml=$sUBB; + + global $emotPath,$cnum,$arrcode,$bUbb2htmlFunctionInit;$cnum=0;$arrcode=array(); + $emotPath='../xheditor_emot/'; + + if(!$bUbb2htmlFunctionInit) + { + function saveCodeArea($match) + { + global $cnum,$arrcode; + $cnum++;$arrcode[$cnum]=$match[0]; + return "[\tubbcodeplace_".$cnum."\t]"; + } + } + + $sHtml=preg_replace_callback('/\[code\s*(?:=\s*((?:(?!")[\s\S])+?)(?:"[\s\S]*?)?)?\]([\s\S]*?)\[\/code\]/i','saveCodeArea',$sHtml); + + //$sHtml=preg_replace("/&/",'&',$sHtml); + //$sHtml=preg_replace("//",'>',$sHtml); + $sHtml=preg_replace("/\r?\n/",'
    ',$sHtml); + + $sHtml=preg_replace("/\[(\/?)(b|u|i|s|sup|sub)\]/i",'<$1$2>',$sHtml); + $sHtml=preg_replace('/\[color\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\]/i','',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getSizeName($match) + { + $arrSize=array('8pt','10pt','12pt','14pt','18pt','24pt','36pt'); + return ''; + } + } + $sHtml=preg_replace_callback("/\[size\s*=\s*(\d+?)\s*\]/i",'getSizeName',$sHtml); + $sHtml=preg_replace('/\[font\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\]/i','',$sHtml); + $sHtml=preg_replace('/\[back\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\]/i','',$sHtml); + $sHtml=preg_replace("/\[\/(color|size|font|back)\]/i",'',$sHtml); + + for($i=0;$i<3;$i++)$sHtml=preg_replace('/\[align\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\](((?!\[align(?:\s+[^\]]+)?\])[\s\S])*?)\[\/align\]/','

    $2

    ',$sHtml); + $sHtml=preg_replace('/\[img\]\s*(((?!")[\s\S])+?)(?:"[\s\S]*?)?\s*\[\/img\]/i','',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getImg($match) + { + $alt=$match[1];$p1=$match[2];$p2=$match[3];$p3=$match[4];$src=$match[5]; + $a=$p3?$p3:(!is_numeric($p1)?$p1:''); + return ''.$alt.''; + } + } + + $sHtml=preg_replace_callback('/\[img\s*=([^,\]]*)(?:\s*,\s*(\d*%?)\s*,\s*(\d*%?)\s*)?(?:,?\s*(\w+))?\s*\]\s*(((?!")[\s\S])+?)(?:"[\s\S]*)?\s*\[\/img\]/i','getImg',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getEmot($match) + { + global $emotPath; + $arr=split(',',$match[1]); + if(!isset($arr[1])){$arr[1]=$arr[0];$arr[0]='default';} + $path=$emotPath.$arr[0].'/'.$arr[1].'.gif'; + return ''.$arr[1].''; + } + } + + $sHtml=preg_replace_callback('/\[emot\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\/\]/i','getEmot',$sHtml); + $sHtml=preg_replace('/\[url\]\s*(((?!")[\s\S])*?)(?:"[\s\S]*?)?\s*\[\/url\]/i','
    $1',$sHtml); + $sHtml=preg_replace('/\[url\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\]\s*([\s\S]*?)\s*\[\/url\]/i','$2',$sHtml); + $sHtml=preg_replace('/\[email\]\s*(((?!")[\s\S])+?)(?:"[\s\S]*?)?\s*\[\/email\]/i','$1',$sHtml); + $sHtml=preg_replace('/\[email\s*=\s*([^\]"]+?)(?:"[^\]]*?)?\s*\]\s*([\s\S]+?)\s*\[\/email\]/i','$2',$sHtml); + $sHtml=preg_replace("/\[quote\]([\s\S]*?)\[\/quote\]/i",'
    $1
    ',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getFlash($match) + { + $w=$match[1];$h=$match[2];$url=$match[3]; + if(!$w)$w=480;if(!$h)$h=400; + return ''; + } + } + $sHtml=preg_replace_callback('/\[flash\s*(?:=\s*(\d+)\s*,\s*(\d+)\s*)?\]\s*(((?!")[\s\S])+?)(?:"[\s\S]*?)?\s*\[\/flash\]/i','getFlash',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getMedia($match) + { + $w=$match[1];$h=$match[2];$play=$match[3];$url=$match[4]; + if(!$w)$w=480;if(!$h)$h=400; + return ''; + } + } + $sHtml=preg_replace_callback('/\[media\s*(?:=\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+)\s*)?)?\]\s*(((?!")[\s\S])+?)(?:"[\s\S]*?)?\s*\[\/media\]/i','getMedia',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getTable($match) + { + return ''; + } + } + $sHtml=preg_replace_callback('/\[table\s*(?:=(\d{1,4}%?)\s*(?:,\s*([^\]"]+)(?:"[^\]]*?)?)?)?\s*\]/i','getTable',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getTR($match){return '';} + } + $sHtml=preg_replace_callback('/\[tr\s*(?:=(\s*[^\]"]+))?(?:"[^\]]*?)?\s*\]/i','getTR',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getTD($match) + { + $col=isset($match[1])?$match[1]:0;$row=isset($match[2])?$match[2]:0;$w=isset($match[3])?$match[3]:null; + return '1?' colspan="'.$col.'"':'').($row>1?' rowspan="'.$row.'"':'').($w?' width="'.$w.'"':'').'>'; + } + } + $sHtml=preg_replace_callback("/\[td\s*(?:=\s*(\d{1,2})\s*,\s*(\d{1,2})\s*(?:,\s*(\d{1,4}%?))?)?\s*\]/i",'getTD',$sHtml); + $sHtml=preg_replace("/\[\/(table|tr|td)\]/i",'',$sHtml); + $sHtml=preg_replace("/\[\*\]((?:(?!\[\*\]|\[\/list\]|\[list\s*(?:=[^\]]+)?\])[\s\S])+)/i",'
  • $1
  • ',$sHtml); + if(!$bUbb2htmlFunctionInit) + { + function getUL($match) + { + $str=''; + } + } + $sHtml=preg_replace_callback('/\[list\s*(?:=\s*([^\]"]+))?(?:"[^\]]*?)?\s*\]/i','getUL',$sHtml); + $sHtml=preg_replace("/\[\/list\]/i",'',$sHtml); + + for($i=1;$i<=$cnum;$i++)$sHtml=str_replace("[\tubbcodeplace_".$i."\t]", $arrcode[$i],$sHtml); + + if(!$bUbb2htmlFunctionInit) + { + function fixText($match) + { + $text=$match[2]; + $text=preg_replace("/\t/",'        ',$text); + $text=preg_replace("/ /",' ',$text); + return $match[1].$text; + } + } + $sHtml=preg_replace_callback('/(^|<\/?\w+(?:\s+[^>]*?)?>)([^<$]+)/i','fixText',$sHtml); + + $bUbb2htmlFunctionInit=true; + + return $sHtml; + } +} diff --git a/trunk/media/save.png b/trunk/media/save.png new file mode 100644 index 0000000000..7e5b6e4977 Binary files /dev/null and b/trunk/media/save.png differ diff --git a/trunk/misc/doc/doxygen/doxygen.conf b/trunk/misc/doc/doxygen/doxygen.conf new file mode 100644 index 0000000000..f1cc53309f --- /dev/null +++ b/trunk/misc/doc/doxygen/doxygen.conf @@ -0,0 +1,1417 @@ +# Doxyfile 1.5.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = ZenTaoPMS + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1.4 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./api.doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, +# and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = framework lib module www bin config + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hiererachy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = YES + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = YES + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is enabled by default, which results in a transparent +# background. Warning: Depending on the platform used, enabling this option +# may lead to badly anti-aliased labels on the edges of a graph (i.e. they +# become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = YES diff --git a/trunk/misc/doc/phpdoc/blank.tpl b/trunk/misc/doc/phpdoc/blank.tpl new file mode 100644 index 0000000000..13b04d7481 --- /dev/null +++ b/trunk/misc/doc/phpdoc/blank.tpl @@ -0,0 +1,13 @@ + + + {$maintitle} + + + + +

    {$maintitle}

    +Welcome to {$package}!
    +
    +This documentation was generated by phpDocumentor v{$phpdocversion}
    + + diff --git a/trunk/misc/doc/phpdoc/header.tpl b/trunk/misc/doc/phpdoc/header.tpl new file mode 100644 index 0000000000..113367583b --- /dev/null +++ b/trunk/misc/doc/phpdoc/header.tpl @@ -0,0 +1,11 @@ + + + + + {$title} + + + + + {if $top3}
    {/if} + diff --git a/trunk/misc/doc/phpdoc/index.tpl b/trunk/misc/doc/phpdoc/index.tpl new file mode 100644 index 0000000000..e70e7c14ff --- /dev/null +++ b/trunk/misc/doc/phpdoc/index.tpl @@ -0,0 +1,23 @@ + + + + + {$title} + + + + + + + + + + + <H2>Frame Alert</H2> + <P>This document is designed to be viewed using the frames feature. + If you see this message, you are using a non-frame-capable web client.</P> + + + diff --git a/trunk/misc/doc/phpdoc/top_frame.tpl b/trunk/misc/doc/phpdoc/top_frame.tpl new file mode 100644 index 0000000000..cfbb0604a6 --- /dev/null +++ b/trunk/misc/doc/phpdoc/top_frame.tpl @@ -0,0 +1,21 @@ + + + + +{$title} + + + + + +{if count($packages) > 1} +Packages + +{/if} +www.zentao.net + + diff --git a/trunk/mobile/corona/Icon-72.png b/trunk/mobile/corona/Icon-72.png new file mode 100644 index 0000000000..8d7207666d Binary files /dev/null and b/trunk/mobile/corona/Icon-72.png differ diff --git a/trunk/mobile/corona/Icon-hdpi.png b/trunk/mobile/corona/Icon-hdpi.png new file mode 100644 index 0000000000..4f08c041af Binary files /dev/null and b/trunk/mobile/corona/Icon-hdpi.png differ diff --git a/trunk/mobile/corona/Icon-ldpi.png b/trunk/mobile/corona/Icon-ldpi.png new file mode 100644 index 0000000000..2d6f3202e0 Binary files /dev/null and b/trunk/mobile/corona/Icon-ldpi.png differ diff --git a/trunk/mobile/corona/Icon-mdpi.png b/trunk/mobile/corona/Icon-mdpi.png new file mode 100644 index 0000000000..baea0e5ef7 Binary files /dev/null and b/trunk/mobile/corona/Icon-mdpi.png differ diff --git a/trunk/mobile/corona/Icon.png b/trunk/mobile/corona/Icon.png new file mode 100644 index 0000000000..2593cbd465 Binary files /dev/null and b/trunk/mobile/corona/Icon.png differ diff --git a/trunk/mobile/corona/Icon@2x.png b/trunk/mobile/corona/Icon@2x.png new file mode 100644 index 0000000000..125d533791 Binary files /dev/null and b/trunk/mobile/corona/Icon@2x.png differ diff --git a/trunk/mobile/corona/Thumbs.db b/trunk/mobile/corona/Thumbs.db new file mode 100644 index 0000000000..c0ef1474be Binary files /dev/null and b/trunk/mobile/corona/Thumbs.db differ diff --git a/trunk/mobile/corona/autoLogin.lua b/trunk/mobile/corona/autoLogin.lua new file mode 100644 index 0000000000..a65d84e0dd --- /dev/null +++ b/trunk/mobile/corona/autoLogin.lua @@ -0,0 +1,103 @@ + +local storyboard = require( "storyboard" ) +local scene = storyboard.newScene() + +loginAPI = "" +sessionAPI = "" +myTodoAPI = "" +myBugAPI = "" +myTaskAPI = "" + +config = {} +session = {} + +function autoLogin() + local response = {} + -- 获取config + local result = http.request {url = configAPI, method = "GET", sink = ltn12.sink.table(response) } + + if table.getn(response) == 0 then + native.showAlert("提示", "网络错误,请查看网络或重新设置!", {"我知道了"} ) + else + for i, val in pairs(response) do + config = json.decode(val) + end + + -- 设置API地址 + if config.requestType == "GET" then + loginAPI = zentaoRoot .. "?m=user&f=login" + sessionAPI = zentaoRoot .. "?m=api&f=getSessionID&t=json" + + myTodoAPI = zentaoRoot .. "?m=my&f=todo&t=json" + myTaskAPI = zentaoRoot .. "?m=my&f=task&t=json" + myBugAPI = zentaoRoot .. "?m=my&f=bug&t=json" + elseif config.requestType == "PATH_INFO" then + loginAPI = zentaoRoot .. "user-login.json?a=1" + sessionAPI = zentaoRoot .. "api-getsessionid.json?a=1" + + myTodoAPI = zentaoRoot .. "my-todo.json?a=1" + myTaskAPI = zentaoRoot .. "my-task.json?a=1" + myBugAPI = zentaoRoot .. "my-bug.json?a=1" + end + + -- 获取session + http.request{url = sessionAPI, method = "GET", sink = ltn12.sink.table(session)} + + if table.getn(session) == 0 then + native.showAlert("提示", "网络错误,请查看网络或重新设置!", {"我知道了"} ) + else + session = json.decode(table.concat(session)) + session.data = json.decode(session.data) + + -- 用户登录 + local response2 = {} + + mixPassword = crypto.digest(crypto.md5, md5Password .. session.data.rand) + loginAPI = loginAPI .. "&account=" .. account .. "&password=" .. mixPassword .. "&" .. session.data.sessionName .. "=" .. session.data.sessionID + http.request{url = loginAPI, method = "GET", sink = ltn12.sink.table(response2) } + + if json.decode(table.concat(response2)).status == "failed" then + native.showAlert("登录失败", "网络问题,或用户信息设置错误!", {"我知道了"}) + else + storyboard.gotoScene(true, "index", "slideLeft", 100) + end + + end + + end +end + + +-- Called when the scene's view does not exist: +function scene:createScene( event ) + local screenGroup = self.view + + zentaoImg = display.newImage("zentao.png") + zentaoImg.xOrigin = display.contentWidth/2 + zentaoImg.yOrigin = display.contentHeight/2 + screenGroup:insert(zentaoImg) + +end + +-- Called immediately after scene has moved onscreen: +function scene:enterScene( event ) + timer.performWithDelay(2000, autoLogin) +end + +-- Called when scene is about to move offscreen: +function scene:exitScene( event ) + local group = self.view +end + +-- Called prior to the removal of scene's "view" (display group) +function scene:destroyScene( event ) + local group = self.view +end + +scene:addEventListener( "createScene", scene ) +scene:addEventListener( "enterScene", scene ) +scene:addEventListener( "exitScene", scene ) +scene:addEventListener( "destroyScene", scene ) + +return scene + diff --git a/trunk/mobile/corona/backButton.png b/trunk/mobile/corona/backButton.png new file mode 100644 index 0000000000..a581b19108 Binary files /dev/null and b/trunk/mobile/corona/backButton.png differ diff --git a/trunk/mobile/corona/backButton_over.png b/trunk/mobile/corona/backButton_over.png new file mode 100644 index 0000000000..db8b9c330e Binary files /dev/null and b/trunk/mobile/corona/backButton_over.png differ diff --git a/trunk/mobile/corona/background.png b/trunk/mobile/corona/background.png new file mode 100644 index 0000000000..4432eef0e8 Binary files /dev/null and b/trunk/mobile/corona/background.png differ diff --git a/trunk/mobile/corona/build.settings b/trunk/mobile/corona/build.settings new file mode 100644 index 0000000000..87c5bc28a3 --- /dev/null +++ b/trunk/mobile/corona/build.settings @@ -0,0 +1,15 @@ +settings = +{ + iphone = + { + plist = + { + CFBundleIconFile = "Icon.png", + CFBundleIconFiles = { + "Icon.png", + "Icon@2x.png", + "Icon-72.png", + }, + }, + } +} \ No newline at end of file diff --git a/trunk/mobile/corona/button.png b/trunk/mobile/corona/button.png new file mode 100644 index 0000000000..5998fb2d3a Binary files /dev/null and b/trunk/mobile/corona/button.png differ diff --git a/trunk/mobile/corona/button_over.png b/trunk/mobile/corona/button_over.png new file mode 100644 index 0000000000..e538e0f5af Binary files /dev/null and b/trunk/mobile/corona/button_over.png differ diff --git a/trunk/mobile/corona/config.lua b/trunk/mobile/corona/config.lua new file mode 100644 index 0000000000..94af14a076 --- /dev/null +++ b/trunk/mobile/corona/config.lua @@ -0,0 +1,12 @@ +-- config.lua + +application = +{ + content = + { + width = 320, + height = 480, + scale = "letterbox" + + }, +} \ No newline at end of file diff --git a/trunk/mobile/corona/dkjson.lua b/trunk/mobile/corona/dkjson.lua new file mode 100644 index 0000000000..987587bbc5 --- /dev/null +++ b/trunk/mobile/corona/dkjson.lua @@ -0,0 +1,806 @@ + --[==[ + +David Kolf's JSON module for Lua 5.1/5.2 +======================================== + +*Version 2.1* + +This module writes no global values, not even the module table. +Import it using + + json = require ("dkjson") + +Exported functions and values: + +`json.encode (object [, state])` +-------------------------------- + +Create a string representing the object. `Object` can be a table, +a string, a number, a boolean, `nil`, `json.null` or any object with +a function `__tojson` in its metatable. A table can only use strings +and numbers as keys and its values have to be valid objects as +well. It raises an error for any invalid data types or reference +cycles. + +`state` is an optional table with the following fields: + + - `indent` + When `indent` (a boolean) is set, the created string will contain + newlines and indentations. Otherwise it will be one long line. + - `keyorder` + `keyorder` is an array to specify the ordering of keys in the + encoded output. If an object has keys which are not in this array + they are written after the sorted keys. + - `level` + This is the initial level of indentation used when `indent` is + set. For each level two spaces are added. When absent it is set + to 0. + - `buffer` + `buffer` is an array to store the strings for the result so they + can be concatenated at once. When it isn't given, the encode + function will create it temporary and will return the + concatenated result. + - `bufferlen` + When `bufferlen` is set, it has to be the index of the last + element of `buffer`. + - `tables` + `tables` is a set to detect reference cycles. It is created + temporary when absent. Every table that is currently processed + is used as key, the value is `true`. + +When `state.buffer` was set, the return value will be `true` on +success. Without `state.buffer` the return value will be a string. + +`json.decode (string [, position [, null]])` +-------------------------------------------- + +Decode `string` starting at `position` or at 1 if `position` was +omitted. + +`null` is an optional value to be returned for null values. The +default is `nil`, but you could set it to `json.null` or any other +value. + +The return values are the object or `nil`, the position of the next +character that doesn't belong to the object, and in case of errors +an error message. + +Two metatables are created. Every array or object that is decoded gets +a metatable with the `__jsontype` field set to either `array` or +`object`. If you want to provide your own metatables use the syntax + + json.decode (string, position, null, objectmeta, arraymeta) + +`.__jsonorder` +------------------------- + +`__jsonorder` can overwrite the `keyorder` for a specific table. + +`.__jsontype` +------------------------ + +`__jsontype` can be either `"array"` or `"object"`. This is mainly useful +for tables that can be empty. (The default for empty tables is +`"array"`). + +`.__tojson (self, state)` +------------------------------------ + +You can provide your own `__tojson` function in a metatable. In this +function you can either add directly to the buffer and return true, +or you can return a string. On errors nil and a message should be +returned. + +`json.null` +----------- + +You can use this value for setting explicit `null` values. + +`json.version` +-------------- + +Set to `"dkjson 2.1"`. + +`json.quotestring (string)` +--------------------------- + +Quote a UTF-8 string and escape critical characters using JSON +escape sequences. This function is only necessary when you build +your own `__tojson` functions. + +`json.addnewline (state)` +------------------------- + +When `state.indent` is set, add a newline to `state.buffer` and spaces +according to `state.level`. + +LPeg support +------------ + +When the local configuration variable +`always_try_using_lpeg` is set, this module tries to load LPeg to +replace the functions `quotestring` and `decode`. The speed increase +is significant. You can get the LPeg module at + . +When LPeg couldn't be loaded, the pure Lua functions stay active. + +In case you don't want this module to require LPeg on its own, +disable this option: + + --]==] + local always_try_using_lpeg = true + --[==[ + +In this case you can later load LPeg support using + +### `json.use_lpeg ()` + +Require the LPeg module and replace the functions `quotestring` and +and `decode` with functions that use LPeg patterns. +This function returns the module table, so you can load the module +using: + + json = require "dkjson".use_lpeg() + +Alternatively you can use `pcall` so the JSON module still works when +LPeg isn't found. + + json = require "dkjson" + pcall (json.use_lpeg) + +### `json.using_lpeg` + +This variable is set to `true` when LPeg was loaded successfully. + +You can contact the author by sending an e-mail to 'kolf' at the +e-mail provider 'gmx.de'. + +--------------------------------------------------------------------- + +*Copyright (C) 2010, 2011 David Heiko Kolf* + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + + diff --git a/trunk/mobile/corona/htmltotext.lua b/trunk/mobile/corona/htmltotext.lua new file mode 100644 index 0000000000..8dfc0e919b --- /dev/null +++ b/trunk/mobile/corona/htmltotext.lua @@ -0,0 +1,96 @@ +function HTML_ToText (text) + -- Declare variables, load the file. Make tags lowercase. + text = string.gsub (text,"(%b<>)", + function (tag) + return tag:lower() + end) + --[[ + First we kill the developer formatting (tabs, CR, LF) + and produce a long string with no newlines and tabs. + We also kill repeated spaces as browsers ignore them anyway. + ]] + local devkill= + { + ["("..string.char(10)..")"] = " ", + ["("..string.char(13)..")"] = " ", + ["("..string.char(15)..")"] = "", + ["(%s%s+)"]=" ", + } + for pat, res in pairs (devkill) do + text = string.gsub (text, pat, res) + end + -- Then we remove the header. We do this by stripping it first. + text = string.gsub (text, "(<%s*head[^>]*>)", "") + text = string.gsub (text, "(<%s*%/%s*head%s*>)", "") + text = string.gsub (text, "(,*<%/head>)", "") + -- Kill all scripts. First we nuke their attribs. + text = string.gsub (text, "(<%s*script[^>]*>)", "") + text = string.gsub (text, "( + +
    +
    + " . $lang->bug->moduleBugs . " "; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=all¶m=0&orderBy=$orderBy&recTotal=0&recPerPage=200"), $lang->bug->allBugs) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=assignToMe¶m=0"), $lang->bug->assignToMe) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=openedByMe¶m=0"), $lang->bug->openedByMe) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=resolvedByMe¶m=0"), $lang->bug->resolvedByMe) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=assignToNull¶m=0"), $lang->bug->assignToNull) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=unResolved¶m=0"), $lang->bug->unResolved) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=unclosed¶m=0"), $lang->bug->unclosed) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=longLifeBugs¶m=0"), $lang->bug->longLifeBugs) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=postponedBugs¶m=0"), $lang->bug->postponedBugs) . ""; + echo "" . html::a($this->createLink('bug', 'browse', "productid=$productID&browseType=needconfirm¶m=0"), $lang->bug->needConfirm) . ""; + echo "{$lang->bug->byQuery} "; + ?> +
    +
    + + + + +
    +
    +
    '>
    + + + + + + + + + +
    +
    +
    + +
    + tree->manage);?> + tree->fix, 'hiddenwin');?> +
    +
    +
    + recTotal}&recPerPage={$pager->recPerPage}"; ?> + + + + + + + + + + cookie->windowWidth >= $this->config->wideSize):?> + + + + + + + + + + cookie->windowWidth >= $this->config->wideSize):?> + + + + + + + + cookie->windowWidth >= $this->config->wideSize):?> + + + + + + + + + + id");?> + + + + + + confirmed;?> + + + cookie->windowWidth >= $this->config->wideSize):?> + + + + + + + + + + cookie->windowWidth >= $this->config->wideSize):?> + + + + + + + + cookie->windowWidth >= $this->config->wideSize):?> + + + + + + + + + + + cookie->windowWidth >= $this->config->wideSize ? 12 : 9;?> + + + +
    idAB);?> bug->severityAB);?> priAB);?>bug->title);?>bug->statusAB);?>bug->story);?>actions;?>openedByAB);?> bug->openedDateAB);?>assignedToAB);?>bug->resolvedByAB);?>bug->resolutionAB);?> bug->resolvedDateAB);?>actions;?>
    id));?>severity;?>'> bug->priList[$bug->pri];?>'> [{$lang->bug->confirmedList[$bug->confirmed]}] " . html::a($bugLink, $bug->title);?>bug->statusList[$bug->status];?>createLink('story', 'view', "stoyID=$bug->story"), $bug->storyTitle, '_blank');?>id"), $lang->confirm, 'hiddenwin')?>openedBy];?>openedDate, 5, 11)?>assignedTo == $this->app->user->account) echo 'class="red"';?>>assignedTo];?>resolvedBy];?>bug->resolutionList[$bug->resolution];?>resolvedDate, 5, 11)?> + id"; + common::printIcon('bug', 'resolve', $params, $bug, 'list'); + common::printIcon('bug', 'close', $params, $bug, 'list'); + common::printIcon('bug', 'edit', $params, $bug, 'list'); + common::printIcon('bug', 'create', "product=$bug->product&extra=bugID=$bug->id", $bug, 'list', 'copy'); + ?> +
    show();?>
    +
    + diff --git a/trunk/module/bug/view/buildtemplates.html.php b/trunk/module/bug/view/buildtemplates.html.php new file mode 100644 index 0000000000..cd04e8e75c --- /dev/null +++ b/trunk/module/bug/view/buildtemplates.html.php @@ -0,0 +1,13 @@ + + $template) +{ + echo ""; + echo $lang->arrow. "$template->title"; + echo html::commonButton('x', "onclick=deleteTemplate($template->id)"); + echo ""; + echo '
    '; +} diff --git a/trunk/module/bug/view/close.html.php b/trunk/module/bug/view/close.html.php new file mode 100644 index 0000000000..678f8e599c --- /dev/null +++ b/trunk/module/bug/view/close.html.php @@ -0,0 +1,31 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
    + + + + + + + + + +
    title;?>
    comment;?>
    + + +
    + +
    + diff --git a/trunk/module/bug/view/confirmbug.html.php b/trunk/module/bug/view/confirmbug.html.php new file mode 100755 index 0000000000..b3ee7aba87 --- /dev/null +++ b/trunk/module/bug/view/confirmbug.html.php @@ -0,0 +1,31 @@ + + * @package bug + * @version $Id: resolve.html.php 1914 2011-06-24 10:11:25Z yidong@cnezsoft.com $ + * @link http://www.zentao.net + */ +?> + +
    + + + + + + + + + +
    title;?>
    comment;?>
    + bug->buttonConfirm);?> + +
    + +
    + diff --git a/trunk/module/bug/view/create.html.php b/trunk/module/bug/view/create.html.php new file mode 100644 index 0000000000..f4dceb3db7 --- /dev/null +++ b/trunk/module/bug/view/create.html.php @@ -0,0 +1,105 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    bug->create;?>
    bug->lblProductAndModule;?> + + +
    bug->project;?>
    bug->openedBuild;?> + + build->notice;?> +
    bug->lblAssignedTo;?>
    bug->title;?>
    bug->steps;?> + + + + + +
    fetch('bug', 'buildTemplates');?>
    +
    bug->lblStory;?> + +
    bug->task;?>
    bug->lblTypeAndSeverity;?> + bug->typeList, $type, 'class=select-2');?> + bug->severityList, $severity, 'class=select-2');?> +
    bug->lblSystemBrowserAndHardware;?> + bug->osList, $os, 'class=select-2');?> + bug->browserList, $browser, 'class=select-2');?> +
    bug->lblMailto;?>
    bug->keywords;?>
    bug->files;?>fetch('file', 'buildform', 'fileCount=2&percent=0.85');?>
    + +
    +
    + diff --git a/trunk/module/bug/view/customfields.html.php b/trunk/module/bug/view/customfields.html.php new file mode 100644 index 0000000000..bcef9b5dbe --- /dev/null +++ b/trunk/module/bug/view/customfields.html.php @@ -0,0 +1,49 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
    + + + + + + + + + + + + + + + +
    bug->customFields;?>
    bug->lblAllFields;?>bug->lblCustomFields;?>
    + + + ', "onclick=\"addItem('allFields', 'customFields')\"") . '
    '; + echo html::commonButton('<', "onclick=delItem('customFields')") . '
    '; + ?> +
    + '; + echo html::commonButton('-', "onclick=downItem('customFields')") . '
    '; + echo html::commonButton($lang->bug->restoreDefault, "onclick=restoreDefault()") . '
    '; + ?> +
    +
    + diff --git a/trunk/module/bug/view/edit.html.php b/trunk/module/bug/view/edit.html.php new file mode 100644 index 0000000000..45ea223c09 --- /dev/null +++ b/trunk/module/bug/view/edit.html.php @@ -0,0 +1,204 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
    +
    +
    + BUG #id . $lang->colon;?> + title), 'class=text-1');?> +
    +
    +
    + + + + + + + +
    + + +
    +
    + bug->legendSteps;?> +
    steps), "rows='12'");?>
    +
    +
    + bug->legendComment;?> + +
    +
    + bug->legendAttatch;?> + fetch('file', 'buildform', 'filecount=2');?> +
    +
    + session->bugList != false ? $app->session->bugList : inlink('browse', "productID=$bug->product"); + echo html::linkButton($lang->goback, $browseLink); + ?> +
    +
    + +
    +
    + bug->legendBasicInfo;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    bug->product;?> + +
    bug->module;?> + +
    bug->type;?>bug->typeList, $bug->type, 'class=select-3');?> +
    bug->severity;?>bug->severityList, $bug->severity, 'class=select-3');?> +
    bug->pri;?>bug->priList, $bug->pri, 'class=select-3');?> +
    bug->status;?>bug->statusList, $bug->status, 'class=select-3');?>
    bug->confirmed;?>bug->confirmedList[$bug->confirmed];?>
    bug->assignedTo;?>assignedTo, 'class=select-3');?>
    bug->os;?>bug->osList, $bug->os, 'class=select-3');?>
    bug->browser;?>bug->browserList, $bug->browser, 'class=select-3');?>
    bug->keywords;?>keywords, 'class="text-3"');?>
    +
    + +
    + bug->legendPrjStoryTask;?> + + + + + + + + + + + + + +
    bug->project;?>project, 'class=select-3 onchange=loadProjectRelated(this.value)');?>
    bug->story;?>
    story, "class=select-3");?>
    +
    bug->task;?>
    task, 'class=select-3');?>
    +
    + +
    + bug->legendLife;?> + + + + + + + + + + + + + + + + + + + + + + + + + + resolution != 'duplicate') echo "style='display:none'";?>> + + + + + + + + + + + +
    bug->openedBy;?>openedBy];?>
    bug->openedBuild;?>openedBuild, 'size=4 multiple=multiple class=select-3');?>
    bug->resolvedBy;?>resolvedBy, 'class=select-3');?>
    bug->resolvedDate;?>resolvedDate, 'class=text-3');?>
    bug->resolvedBuild;?>resolvedBuild, 'class=select-3');?>
    bug->resolution;?>bug->resolutionList, $bug->resolution, 'class=select-3 onchange=setDuplicate(this.value)');?>
    bug->duplicateBug;?>duplicateBug, 'class=text-3');?>
    bug->closedBy;?>closedBy, 'class=select-3');?>
    bug->closedDate;?>closedDate, 'class=text-3');?>
    +
    +
    + bug->legendMisc;?> + + + + + + + + + + + + + +
    bug->mailto;?>mailto, 'class="text-3"');?>
    bug->linkBug;?>linkBug, 'class="text-3"');?>
    bug->case;?>case, 'class="text-3"');?>
    +
    +
    + diff --git a/trunk/module/bug/view/export.html.php b/trunk/module/bug/view/export.html.php new file mode 100755 index 0000000000..3f1bb625f8 --- /dev/null +++ b/trunk/module/bug/view/export.html.php @@ -0,0 +1,13 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + diff --git a/trunk/module/bug/view/report.html.php b/trunk/module/bug/view/report.html.php new file mode 100644 index 0000000000..a4b81ff59e --- /dev/null +++ b/trunk/module/bug/view/report.html.php @@ -0,0 +1,62 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
    +
    bug->report->common;?>
    +
    goback); ?>
    +
    + + + + + + + +
    +
    bug->report->select;?>
    +
    + + bug->report->charts, $checkedCharts);?> + + +

    + bug->report->create);?> +
    +
    + + + $chartContent):?> + + + + + +
    bug->report->common;?>
    + + + + + + + $data):?> + + + + + + +
    report->item;?>report->value;?>report->percent;?>
    name;?>value;?>percent * 100) . '%';?>
    +
    +
    + + diff --git a/trunk/module/bug/view/resolve.html.php b/trunk/module/bug/view/resolve.html.php new file mode 100644 index 0000000000..dd080dbd70 --- /dev/null +++ b/trunk/module/bug/view/resolve.html.php @@ -0,0 +1,51 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    title;?>
    bug->resolution;?>bug->resolutionList['tostory']); echo html::select('resolution', $lang->bug->resolutionList, '', 'class=select-3 onchange=setDuplicate(this.value)');?>
    bug->resolvedBuild;?>
    bug->resolvedDate;?>
    bug->assignedTo;?>openedBy, 'class=select-3');?>
    comment;?>
    + + +
    + +
    + diff --git a/trunk/module/bug/view/sendmail.html.php b/trunk/module/bug/view/sendmail.html.php new file mode 100644 index 0000000000..1dc7cbb0bc --- /dev/null +++ b/trunk/module/bug/view/sendmail.html.php @@ -0,0 +1,38 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + diff --git a/trunk/module/convert/view/checkconfig.html.php b/trunk/module/convert/view/checkconfig.html.php new file mode 100644 index 0000000000..f9ffd9d97f --- /dev/null +++ b/trunk/module/convert/view/checkconfig.html.php @@ -0,0 +1,29 @@ + + * @package convert + * @version $Id$ + */ +?> + +'> +
    + BUG #id . "=>$bug->assignedTo " . html::a(common::getSysURL() . $this->createLink('bug', 'view', "bugID=$bug->id"), $bug->title);?> +
    +
    + bug->legendSteps;?> +
    + steps, '>BUG #id . ' ' . $bug->title;?>
    +
    + session->bugList != false ? $app->session->bugList : inlink('browse', "productID=$bug->product"); + $params = "bugID=$bug->id"; + $copyParams = "productID=$productID&extras=bugID=$bug->id"; + $convertParams = "productID=$productID&moduleID=0&from=bug&bugID=$bug->id"; + if(!$bug->deleted) + { + ob_start(); + if($this->bug->isClickable($bug, 'confirmBug')) common::printIcon('bug', 'confirmBug', $params); + common::printIcon('bug', 'assignTo', $params); + if($this->bug->isClickable($bug, 'resolve')) common::printIcon('bug', 'resolve', $params); + if($this->bug->isClickable($bug, 'close')) common::printIcon('bug', 'close', $params); + if($this->bug->isClickable($bug, 'activate')) common::printIcon('bug', 'activate', $params); + + if($this->bug->isClickable($bug, 'toStory')) common::printIcon('bug', 'toStory', "product=$bug->product&module=0&story=0&project=0&bugID=$bug->id", '', 'button', 'toStory'); + common::printIcon('bug', 'createCase', $convertParams, '', 'button', 'createCase'); + + common::printDivider(); + common::printIcon('bug', 'edit', $params); + common::printCommentIcon('bug'); + common::printIcon('bug', 'create', $copyParams, '', 'button', 'copy'); + common::printIcon('bug', 'delete', $params, '', 'button', '', 'hiddenwin'); + + common::printDivider(); + common::printRPN($browseLink, $preAndNext); + + $actionLinks = ob_get_contents(); + ob_end_clean(); + echo $actionLinks; + } + ?> +
    + + + + + + + + +
    +
    + bug->legendSteps;?> +
    [', '

    [', $bug->steps);?>

    +
    + fetch('file', 'printFiles', array('files' => $bug->files, 'fieldset' => 'true'));?> + + + +
    +
    + bug->legendBasicInfo;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    bug->product;?>product", $productName)) echo $productName;?> +
    bug->module;?> + $module) + { + if(!common::printLink('bug', 'browse', "productID=$bug->product&browseType=byModule¶m=$module->id", $module->name)) echo $module->name; + if(isset($modulePath[$key + 1])) echo $lang->arrow; + } + ?> +
    bug->type;?>bug->typeList[$bug->type])) echo $lang->bug->typeList[$bug->type]; else echo $bug->type;?>
    bug->severity;?>bug->severityList[$bug->severity];?>
    bug->pri;?>bug->priList[$bug->pri];?>
    bug->status;?>bug->statusList[$bug->status];?>
    bug->activatedCount;?>activatedCount;?>
    bug->confirmed;?>bug->confirmedList[$bug->confirmed];?>
    bug->lblAssignedTo;?>assignedTo) echo $users[$bug->assignedTo] . $lang->at . $bug->assignedDate;?>
    bug->os;?>bug->osList[$bug->os];?>
    bug->browser;?>bug->browserList[$bug->browser];?>
    bug->keywords;?>keywords;?>
    +
    +
    + bug->legendLife;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    bug->openedBy;?> openedBy] . $lang->at . $bug->openedDate;?>
    bug->openedBuild;?> + openedBuild) + { + $openedBuilds = explode(',', $bug->openedBuild); + foreach($openedBuilds as $openedBuild) isset($builds[$openedBuild]) ? print($builds[$openedBuild] . '
    ') : print($openedBuild . '
    '); + } + else + { + echo $bug->openedBuild; + } + ?> +
    bug->lblResolved;?>resolvedBy) echo $users[$bug->resolvedBy] . $lang->at . $bug->resolvedDate;?> +
    bug->resolvedBuild;?>resolvedBuild])) echo $builds[$bug->resolvedBuild]; else echo $bug->resolvedBuild;?>
    bug->resolution;?> + bug->resolutionList[$bug->resolution]; + if(isset($bug->duplicateBugTitle)) echo " #$bug->duplicateBug:" . html::a($this->createLink('bug', 'view', "bugID=$bug->duplicateBug"), $bug->duplicateBugTitle); + ?> +
    bug->closedBy;?>closedBy) echo $users[$bug->closedBy] . $lang->at . $bug->closedDate;?>
    bug->lblLastEdited;?>lastEditedBy) echo $users[$bug->lastEditedBy] . $lang->at . $bug->lastEditedDate?>
    +
    + +
    + bug->legendPrjStoryTask;?> + + + + + + + + + + + + + +
    bug->project;?>project) echo html::a($this->createLink('project', 'browse', "projectid=$bug->project"), $bug->projectName);?>
    bug->story;?> + story) echo html::a($this->createLink('story', 'view', "storyID=$bug->story"), "#$bug->story $bug->storyTitle"); + if($bug->storyStatus == 'active' and $bug->latestStoryVersion > $bug->storyVersion) + { + echo "({$lang->story->changed} "; + echo html::a($this->createLink('bug', 'confirmStoryChange', "bugID=$bug->id"), $lang->confirm, 'hiddenwin'); + echo ")"; + } + ?> +
    bug->task;?>task) echo html::a($this->createLink('task', 'view', "taskID=$bug->task"), $bug->taskName);?>
    +
    +
    + bug->legendMisc;?> + + + + + + + + + + + + + + + + + + + + + +
    bug->mailto;?>mailto)); foreach($mailto as $account) echo ' ' . $users[$account]; ?>
    bug->linkBug;?> + linkBugTitles)) + { + foreach($bug->linkBugTitles as $linkBugID => $linkBugTitle) + { + echo html::a($this->createLink('bug', 'view', "bugID=$linkBugID"), "#$linkBugID $linkBugTitle", '_blank') . '
    '; + } + } + ?> +
    bug->case;?>caseTitle)) echo html::a($this->createLink('testcase', 'view', "caseID=$bug->case"), "#$bug->case $bug->caseTitle", '_blank');?>
    bug->toStory;?>toStory != 0) echo html::a($this->createLink('story', 'view', "storyID=$bug->toStory"), "#$bug->toStory $bug->toStoryTitle", '_blank');?>
    bug->toTask;?>toTask != 0) echo html::a($this->createLink('task', 'view', "taskID=$bug->toTask"), "#$bug->toTask $bug->toTaskTitle", '_blank');?>
    +
    +
    + diff --git a/trunk/module/build/config.php b/trunk/module/build/config.php new file mode 100644 index 0000000000..86c6b64a19 --- /dev/null +++ b/trunk/module/build/config.php @@ -0,0 +1,6 @@ +build->create->requiredFields = 'product,name,builder,date'; +$config->build->edit->requiredFields = 'product,name,builder,date'; + +$config->build->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->build->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/trunk/module/build/control.php b/trunk/module/build/control.php new file mode 100644 index 0000000000..ca45408850 --- /dev/null +++ b/trunk/module/build/control.php @@ -0,0 +1,192 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +class build extends control +{ + /** + * Create a buld. + * + * @param int $projectID + * @access public + * @return void + */ + public function create($projectID) + { + if(!empty($_POST)) + { + $buildID = $this->build->create($projectID); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action')->create('build', $buildID, 'opened'); + die(js::locate($this->createLink('project', 'build', "project=$projectID"), 'parent')); + } + + /* Load these models. */ + $this->loadModel('story'); + $this->loadModel('bug'); + $this->loadModel('task'); + $this->loadModel('project'); + $this->loadModel('user'); + + /* Set menu. */ + $this->project->setMenu($this->project->getPairs(), $projectID); + + /* Get stories and bugs. */ + $orderBy = 'status_asc, stage_asc, id_desc'; + $stories = $this->story->getProjectStories($projectID, $orderBy); + $bugs = $this->bug->getProjectBugs($projectID); + + /* Assign. */ + $this->view->header->title = $this->lang->build->create; + $this->view->products = $this->project->getProducts($projectID); + $this->view->projectID = $projectID; + $this->view->users = $this->user->getPairs(); + $this->view->stories = $stories; + $this->view->bugs = $bugs; + $this->view->orderBy = $orderBy; + $this->display(); + } + + /** + * Edit a build. + * + * @param int $buildID + * @access public + * @return void + */ + public function edit($buildID) + { + if(!empty($_POST)) + { + $changes = $this->build->update($buildID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('build', $buildID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate(inlink('view', "buildID=$buildID"), 'parent')); + } + + $this->loadModel('story'); + $this->loadModel('bug'); + $this->loadModel('project'); + + /* Set menu. */ + $build = $this->build->getById((int)$buildID); + $this->project->setMenu($this->project->getPairs(), $build->project); + + /* Get stories and bugs. */ + $orderBy = 'status_asc, stage_asc, id_desc'; + $stories = $this->story->getProjectStories($build->project, $orderBy); + $bugs = $this->bug->getProjectBugs($build->project); + + /* Assign. */ + $this->view->header->title = $this->lang->build->edit; + $this->view->position[] = $this->lang->build->edit; + $this->view->products = $this->project->getProducts($build->project); + $this->view->users = $this->loadModel('user')->getPairs(); + $this->view->build = $build; + $this->view->stories = $stories; + $this->view->bugs = $bugs; + $this->view->orderBy = $orderBy; + $this->display(); + } + + /** + * View a build. + * + * @param int $buildID + * @access public + * @return void + */ + public function view($buildID) + { + $this->loadModel('story'); + $this->loadModel('bug'); + + /* Set menu. */ + $build = $this->build->getById((int)$buildID, true); + if(!$build) die(js::error($this->lang->notFound) . js::locate('back')); + + $stories = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($build->stories)->fetchAll(); + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story'); + + $bugs = $this->dao->select('*')->from(TABLE_BUG)->where('id')->in($build->bugs)->fetchAll(); + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug'); + + $this->loadModel('project')->setMenu($this->project->getPairs(), $build->project); + + /* Assign. */ + $projects = $this->project->getPairs(); + $this->view->header->title = "BUILD #$build->id $build->name - " . $projects[$build->project]; + $this->view->position[] = $this->lang->build->view; + $this->view->products = $this->project->getProducts($build->project); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->build = $build; + $this->view->stories = $stories; + $this->view->bugs = $bugs; + $this->view->actions = $this->loadModel('action')->getList('build', $buildID); + $this->display(); + } + + /** + * Delete a build. + * + * @param int $buildID + * @param string $confirm yes|noe + * @access public + * @return void + */ + public function delete($buildID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->build->confirmDelete, $this->createLink('build', 'delete', "buildID=$buildID&confirm=yes"))); + } + else + { + $build = $this->build->getById($buildID); + $this->build->delete(TABLE_BUILD, $buildID); + die(js::locate($this->createLink('project', 'build', "projectID=$build->project"), 'parent')); + } + } + + /** + * AJAX: get builds of a product in html select. + * + * @param int $productID + * @param string $varName the name of the select object to create + * @param string $build build to selected + * @access public + * @return string + */ + public function ajaxGetProductBuilds($productID, $varName, $build = '') + { + if($varName == 'openedBuild') die(html::select($varName . '[]', $this->build->getProductBuildPairs($productID, 'noempty'), $build, 'size=4 class=select-3 multiple')); + if($varName == 'resolvedBuild') die(html::select($varName, $this->build->getProductBuildPairs($productID, 'noempty'), $build, 'class=select-3')); + } + + /** + * AJAX: get builds of a project in html select. + * + * @param int $projectID + * @param string $varName the name of the select object to create + * @param string $build build to selected + * @access public + * @return string + */ + public function ajaxGetProjectBuilds($projectID, $productID, $varName, $build = '') + { + if($varName == 'openedBuild') die(html::select($varName . '[]', $this->build->getProjectBuildPairs($projectID, $productID, 'noempty'), $build, 'size=4 class=select-3 multiple')); + if($varName == 'resolvedBuild') die(html::select($varName, $this->build->getProjectBuildPairs($projectID, $productID, 'noempty'), $build, 'class=select-3')); + if($varName == 'testTaskBuild') die(html::select('build', $this->build->getProjectBuildPairs($projectID, $productID, 'noempty'), $build, 'class=select-3')); + } +} diff --git a/trunk/module/build/css/common.css b/trunk/module/build/css/common.css new file mode 100644 index 0000000000..e8abbee4f8 --- /dev/null +++ b/trunk/module/build/css/common.css @@ -0,0 +1,3 @@ +.mainTable { width:100%; border:none} +.headTable { width:100%; height:30px; margin:0px;} +.contentDiv { height:195px; overflow-y:auto} diff --git a/trunk/module/build/js/common.js b/trunk/module/build/js/common.js new file mode 100644 index 0000000000..5cafeccc97 --- /dev/null +++ b/trunk/module/build/js/common.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + $("a.preview").colorbox({width:1000, height:600, iframe:true, transition:'elastic', speed:350, scrolling:true}); +}) diff --git a/trunk/module/build/lang/en.php b/trunk/module/build/lang/en.php new file mode 100644 index 0000000000..3f38c5da77 --- /dev/null +++ b/trunk/module/build/lang/en.php @@ -0,0 +1,42 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->build->common = 'Build'; +$lang->build->create = "Create"; +$lang->build->edit = "Edit"; +$lang->build->delete = "Delete"; +$lang->build->view = "Info"; +$lang->build->ajaxGetProductBuilds = 'API:Product builds'; +$lang->build->ajaxGetProjectBuilds = 'API:Project builds'; + +$lang->build->confirmDelete = "Are sure to delete this build?"; + +$lang->build->basicInfo='Basic Info'; + +$lang->build->id = 'ID'; +$lang->build->product = 'Product'; +$lang->build->project = 'Project'; +$lang->build->name = 'Name'; +$lang->build->date = 'Build date'; +$lang->build->builder = 'Builder'; +$lang->build->scmPath = 'Source code path'; +$lang->build->filePath = 'Package file path'; +$lang->build->desc = 'Desc'; +$lang->build->linkStoriesAndBugs = 'stories and bugs'; +$lang->build->linkStories = 'Stories'; +$lang->build->linkBugs = 'Bugs'; +$lang->build->stories = 'Linked stories'; +$lang->build->bugs = 'Linked bugs'; + +$lang->build->finishStories = 'The total demand for a complete %s'; +$lang->build->resolvedBugs = 'The total solution of bug%s'; + +$lang->build->notice = "Please create a build in project view's build page."; diff --git a/trunk/module/build/lang/zh-cn.php b/trunk/module/build/lang/zh-cn.php new file mode 100644 index 0000000000..8ee155d78a --- /dev/null +++ b/trunk/module/build/lang/zh-cn.php @@ -0,0 +1,42 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->build->common = "Build"; +$lang->build->create = "创建版本"; +$lang->build->edit = "编辑版本"; +$lang->build->delete = "删除版本"; +$lang->build->view = "版本详情"; +$lang->build->ajaxGetProductBuilds = '接口:产品版本列表'; +$lang->build->ajaxGetProjectBuilds = '接口:项目版本列表'; + +$lang->build->confirmDelete = "您确认删除该版本吗?"; + +$lang->build->basicInfo ='基本信息'; + +$lang->build->id = 'ID'; +$lang->build->product = '产品'; +$lang->build->project = '项目'; +$lang->build->name = '名称编号'; +$lang->build->date = '打包日期'; +$lang->build->builder = '构建者'; +$lang->build->scmPath = '源代码地址'; +$lang->build->filePath = '存储地址'; +$lang->build->desc = '描述'; +$lang->build->linkStoriesAndBugs = '关联需求和Bug'; +$lang->build->linkStories = '相关需求'; +$lang->build->linkBugs = '相关Bug'; +$lang->build->stories = '已关联需求'; +$lang->build->bugs = '已关联Bug'; + +$lang->build->finishStories = '本次共完成需求%s个'; +$lang->build->resolvedBugs = '本次共解决Bug%s个'; + +$lang->build->notice = '版本请到[项目视图]-[版本]创建。'; diff --git a/trunk/module/build/lang/zh-tw.php b/trunk/module/build/lang/zh-tw.php new file mode 100644 index 0000000000..a03b301f37 --- /dev/null +++ b/trunk/module/build/lang/zh-tw.php @@ -0,0 +1,42 @@ + + * @package build + * @version $Id: zh-tw.php 3309 2012-07-03 02:53:55Z wwccss $ + * @link http://www.zentao.net + */ +$lang->build->common = "Build"; +$lang->build->create = "創建版本"; +$lang->build->edit = "編輯版本"; +$lang->build->delete = "刪除版本"; +$lang->build->view = "版本詳情"; +$lang->build->ajaxGetProductBuilds = '介面:產品版本列表'; +$lang->build->ajaxGetProjectBuilds = '介面:項目版本列表'; + +$lang->build->confirmDelete = "您確認刪除該版本嗎?"; + +$lang->build->basicInfo ='基本信息'; + +$lang->build->id = 'ID'; +$lang->build->product = '產品'; +$lang->build->project = '項目'; +$lang->build->name = '名稱編號'; +$lang->build->date = '打包日期'; +$lang->build->builder = '構建者'; +$lang->build->scmPath = '原始碼地址'; +$lang->build->filePath = '存儲地址'; +$lang->build->desc = '描述'; +$lang->build->linkStoriesAndBugs = '關聯需求和Bug'; +$lang->build->linkStories = '相關需求'; +$lang->build->linkBugs = '相關Bug'; +$lang->build->stories = '已關聯需求'; +$lang->build->bugs = '已關聯Bug'; + +$lang->build->finishStories = '本次共完成需求%s個'; +$lang->build->resolvedBugs = '本次共解決Bug%s個'; + +$lang->build->notice = '版本請到[項目視圖]-[版本]創建。'; diff --git a/trunk/module/build/model.php b/trunk/module/build/model.php new file mode 100644 index 0000000000..35f02f5a35 --- /dev/null +++ b/trunk/module/build/model.php @@ -0,0 +1,155 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +?> +dao->select('t1.*, t2.name as projectName, t3.name as productName') + ->from(TABLE_BUILD)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->where('t1.id')->eq((int)$buildID) + ->orderBy('t1.id DESC') + ->fetch(); + if(!$build) return false; + if($setImgSize) $build->desc = $this->loadModel('file')->setImgSize($build->desc); + return $build; + } + + /** + * Get builds of a project. + * + * @param int $projectID + * @access public + * @return array + */ + public function getProjectBuilds($projectID) + { + return $this->dao->select('t1.*, t2.name as projectName, t3.name as productName') + ->from(TABLE_BUILD)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->where('t1.project')->eq((int)$projectID) + ->andWhere('t1.deleted')->eq(0) + ->orderBy('t1.date DESC, t1.id desc') + ->fetchAll(); + } + + /** + * Get builds of a project in pairs. + * + * @param int $projectID + * @param int $productID + * @param string $params noempty|notrunk, can be a set of them + * @access public + * @return array + */ + public function getProjectBuildPairs($projectID, $productID, $params = '') + { + $sysBuilds = array(); + if(strpos($params, 'noempty') === false) $sysBuilds = array('' => ''); + if(strpos($params, 'notrunk') === false) $sysBuilds = $sysBuilds + array('trunk' => 'Trunk'); + + $builds = $this->dao->select('id,name')->from(TABLE_BUILD) + ->where('project')->eq((int)$projectID) + ->beginIF($productID)->andWhere('product')->eq((int)$productID)->fi() + ->andWhere('deleted')->eq(0) + ->orderBy('date desc, id desc')->fetchPairs(); + if(!$builds) return $sysBuilds; + $releases = $this->dao->select('build,name')->from(TABLE_RELEASE) + ->where('build')->in(array_keys($builds)) + ->andWhere('deleted')->eq(0) + ->fetchPairs(); + foreach($releases as $buildID => $releaseName) $builds[$buildID] = $releaseName; + return $sysBuilds + $builds; + } + + /** + * Get builds of a product in pairs. + * + * @param int $productID + * @param string $params noempty|notrunk, can be a set of them + * @access public + * @return string + */ + public function getProductBuildPairs($productID, $params = '') + { + $sysBuilds = array(); + if(strpos($params, 'noempty') === false) $sysBuilds = array('' => ''); + if(strpos($params, 'notrunk') === false) $sysBuilds = $sysBuilds + array('trunk' => 'Trunk'); + + $builds = $this->dao->select('id,name')->from(TABLE_BUILD) + ->where('product')->eq((int)$productID) + ->andWhere('deleted')->eq(0) + ->orderBy('date desc, id desc')->fetchPairs(); + if(!$builds) return $sysBuilds; + return $sysBuilds + $builds; + } + + /** + * Create a build + * + * @param int $projectID + * @access public + * @return void + */ + public function create($projectID) + { + $build->stories = ''; + $build->bugs = ''; + + $build = fixer::input('post')->stripTags('name') + ->join('stories', ',') + ->join('bugs', ',') + ->remove('allchecker') + ->add('project', (int)$projectID)->get(); + $this->dao->insert(TABLE_BUILD)->data($build)->autoCheck()->batchCheck($this->config->build->create->requiredFields, 'notempty')->check('name','unique')->exec(); + if(!dao::isError()) return $this->dao->lastInsertID(); + } + + /** + * Update a build. + * + * @param int $buildID + * @access public + * @return void + */ + public function update($buildID) + { + $oldBuild = $this->getByID($buildID); + $build = fixer::input('post') + ->stripTags('name') + ->setDefault('stories', '') + ->setDefault('bugs', '') + ->join('stories', ',') + ->join('bugs', ',') + ->remove('allchecker') + ->get(); + $this->dao->update(TABLE_BUILD)->data($build) + ->autoCheck() + ->batchCheck($this->config->build->edit->requiredFields, 'notempty') + ->where('id')->eq((int)$buildID) + ->check('name','unique', "id != $buildID") + ->exec(); + if(!dao::isError()) return common::createChanges($oldBuild, $build); + } +} diff --git a/trunk/module/build/view/create.html.php b/trunk/module/build/view/create.html.php new file mode 100644 index 0000000000..c5865b276e --- /dev/null +++ b/trunk/module/build/view/create.html.php @@ -0,0 +1,132 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    build->create;?>
    build->product;?>
    build->name;?>
    build->builder;?>user->account, 'class="select-3"');?>
    build->date;?>
    build->scmPath;?>
    build->filePath;?>
    build->linkStoriesAndBugs;?> +
    + +
    + + + + + + + +
    + + + + + + + + +
    build->linkStories;?>
    idAB;?>story->title;?>statusAB;?>story->stageAB;?>
    +
    +
    + + $story):?> + createLink('story', 'view', "storyID=$story->id"); + ?> + + + + + + + +
    + stage == 'developed' or $story->status == 'closed') echo 'checked';?>> id);?> + title, '', "class='preview'");?>story->statusList[$story->status];?>story->stageList[$story->stage];?>
    +
    +
    +
    + +
    + + + + + + + +
    + + + + + + + +
    build->linkBugs;?>
    idAB;?>bug->title;?>bug->status;?>
    +
    +
    + + + createLink('bug', 'view', "bugID=$bug->id");?> + + + + + + +
    + status == 'resolved' or $bug->status == 'closed') echo "checked";?>> id);?> + title, '', "class='preview'");?>bug->statusList[$bug->status];?>
    +
    +
    +
    + +
    +
    build->desc;?>
    +
    + diff --git a/trunk/module/build/view/edit.html.php b/trunk/module/build/view/edit.html.php new file mode 100644 index 0000000000..e0cb343dd4 --- /dev/null +++ b/trunk/module/build/view/edit.html.php @@ -0,0 +1,126 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    build->edit;?>
    build->product;?>product, "class='select-3'");?>
    build->name;?>name, "class='text-3'");?>
    build->builder;?>builder, 'class="select-3"');?>
    build->date;?>date, "class='text-3 date'");?>
    build->scmPath;?>scmPath, "class='text-1'");?>
    build->filePath;?>filePath, "class='text-1'");?>
    build->linkStoriesAndBugs;?> +
    + +
    + + + + + + + +
    + + + + + + + + +
    build->linkStories;?>
    idAB;?>story->title;?>statusAB;?>story->stageAB;?>
    +
    +
    + + $story):?> + createLink('story', 'view', "storyID=$story->id");?> + + + + + + + +
    stories, $story->id) !== false) echo 'checked';?>> id);?>title, '', "class='preview'");?>story->statusList[$story->status];?>story->stageList[$story->stage];?>
    +
    +
    +
    + +
    + + + + + + + +
    + + + + + + + +
    build->linkBugs;?>
    idAB;?>bug->title;?>bug->status;?>
    +
    +
    + + + createLink('bug', 'view', "bugID=$bug->id");?> + + + + + + +
    bugs, $bug->id) !== false) echo 'checked';?>> id);?>title, '', "class='preview'");?>bug->statusList[$bug->status];?>
    +
    +
    +
    + +
    +
    build->desc;?>desc), "rows='15' class='area-1'");?>
    project);?>
    +
    + diff --git a/trunk/module/build/view/view.html.php b/trunk/module/build/view/view.html.php new file mode 100644 index 0000000000..27b7e07961 --- /dev/null +++ b/trunk/module/build/view/view.html.php @@ -0,0 +1,119 @@ + + * @package build + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
    BUILD #id . ' ' . $build->name;?>
    +
    + build->desc;?> +
    desc;?>
    +
    + +
    + session->buildList ? $this->session->buildList : $this->createLink('project', 'build', "projectID=$build->project"); + if(!$build->deleted) + { + common::printLink('build', 'edit', "buildID=$build->id", $lang->edit); + common::printLink('build', 'delete', "buildID=$build->id", $lang->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
    + + + + + + + + + + + + $story):?> + createLink('story', 'view', "storyID=$story->id");?> + + + + + + + + + + + +
    build->stories;?>
    idAB;?>priAB;?>story->title;?>openedByAB;?>story->estimateAB;?>statusAB;?>story->stageAB;?>
    id);?>story->priList[$story->pri];?>title, '', "class='preview'");?>openedBy];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?>
    build->finishStories, count($stories));?>
    + + + + + + + + + + + + + createLink('bug', 'view', "bugID=$bug->id");?> + + + + + + + + + + + +
    build->bugs;?>
    idAB;?>bug->title;?>bug->status;?>openedByAB;?>bug->openedDateAB;?>bug->resolvedByAB;?>bug->resolvedDateAB;?>
    id);?>title, '', "class='preview'");?>bug->statusList[$bug->status];?>openedBy];?>openedDate, 5, 11)?>resolvedBy];?>resolvedDate, 5, 11)?>
    build->resolvedBugs, count($bugs));?>
    +
    +
    + build->basicInfo?> + + + + + + + + + + + + + + + + + + + + + + + + + +
    build->product;?>productName;?>
    build->name;?>'>name;?>
    build->builder;?>builder];?>
    build->date;?>date;?>
    build->scmPath;?>scmPath, 'http') === 0 ? printf(html::a($build->scmPath)) : printf($build->scmPath);?>
    build->filePath;?>filePath, 'http') === 0 ? printf(html::a($build->filePath)) : printf($build->filePath);?>
    +
    +
    + diff --git a/trunk/module/common/control.php b/trunk/module/common/control.php new file mode 100644 index 0000000000..b0a11f1211 --- /dev/null +++ b/trunk/module/common/control.php @@ -0,0 +1,414 @@ + + * @package common + * @version $Id$ + * @link http://www.zentao.net + */ +class common extends control +{ + /** + * The construc method, to do some auto things. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->common->startSession(); + $this->common->sendHeader(); + $this->common->setCompany(); + $this->common->setUser(); + $this->common->loadConfigFromDB(); + $this->app->loadLang('company'); + } + + /** + * Check the user has permission to access this method, if not, locate to the login page or deny page. + * + * @access public + * @return void + */ + public function checkPriv() + { + $module = $this->app->getModuleName(); + $method = $this->app->getMethodName(); + if($this->common->isOpenMethod($module, $method)) return true; + if(!$this->loadModel('user')->isLogon() and $this->server->php_auth_user) $this->user->identifyByPhpAuth(); + if(!$this->loadModel('user')->isLogon() and $this->cookie->za) $this->user->identifyByCookie(); + + if(isset($this->app->user)) + { + if(!common::hasPriv($module, $method)) $this->common->deny($module, $method); + } + else + { + $referer = helper::safe64Encode($this->app->getURI(true)); + $this->locate($this->createLink('user', 'login', "referer=$referer")); + } + } + + /** + * Check upgrade's status file is ok or not. + * + * @access public + * @return void + */ + public function checkUpgradeStatus() + { + $statusFile = $this->app->getAppRoot() . 'www' . $this->pathFix . 'ok'; + if(!file_exists($statusFile) or time() - filemtime($statusFile) > 3600) + { + $this->app->loadLang('upgrade'); + echo ""; + echo "
    "; + printf($this->lang->upgrade->setStatusFile, $statusFile, $statusFile, $statusFile); + die('
    '); + } + } + + /** + * Check the user has permisson of one method of one module. + * + * @param string $module + * @param string $method + * @static + * @access public + * @return bool + */ + public static function hasPriv($module, $method) + { + global $app; + + /* Check is the super admin or not. */ + $account = ',' . $app->user->account . ','; + if(strpos($app->company->admins, $account) !== false) return true; + + /* If not super admin, check the rights. */ + $rights = $app->user->rights; + if(isset($rights[strtolower($module)][strtolower($method)])) return true; + return false; + } + + /** + * Replace the %s of one key of a menu by $params. + * + * All the menus are defined in the common's language file. But there're many dynamic params, so in the defination, + * we used %s as placeholder. These %s should be setted in one module. + * + * The items of one module's menu may be an string or array. For example, please see module/common/lang. + * + * @param string $object the menus of one module + * @param string $key the menu item to be replaced + * @param string $params the params passed to the menu item + * @access public + * @return void + */ + public function setMenuVars($menu, $key, $params) + { + if(is_array($params)) + { + if(is_array($menu->$key)) + { + $menu->$key = (object)$menu->$key; + $menu->$key->link = vsprintf($menu->$key->link, $params); + $menu->$key = (array)$menu->$key; + } + else + { + $menu->$key = vsprintf($menu->$key, $params); + } + } + else + { + if(is_array($menu->$key)) + { + $menu->$key = (object)$menu->$key; + $menu->$key->link = sprintf($menu->$key->link, $params); + $menu->$key = (array)$menu->$key; + } + else + { + $menu->$key = sprintf($menu->$key, $params); + } + } + } + + /** + * Print the link contains orderBy field. + * + * This method will auto set the orderby param according the params. Fox example, if the order by is desc, + * will be changed to asc. + * + * @param string $fieldName the field name to sort by + * @param string $orderBy the order by string + * @param string $vars the vars to be passed + * @param string $label the label of the link + * @param string $module the module name + * @param string $method the method name + * @static + * @access public + * @return void + */ + public static function printOrderLink($fieldName, $orderBy, $vars, $label, $module = '', $method = '') + { + global $lang, $app; + if(empty($module)) $module= $app->getModuleName(); + if(empty($method)) $method= $app->getMethodName(); + $className = 'header'; + + if(strpos($orderBy, $fieldName) !== false) + { + if(stripos($orderBy, 'desc') !== false) + { + $orderBy = str_ireplace('desc', 'asc', $orderBy); + $className = 'headerSortUp'; + } + elseif(stripos($orderBy, 'asc') !== false) + { + $orderBy = str_ireplace('asc', 'desc', $orderBy); + $className = 'headerSortDown'; + } + } + else + { + $orderBy = $fieldName . '_' . 'asc'; + $className = 'header'; + } + $link = helper::createLink($module, $method, sprintf($vars, $orderBy)); + echo "
    " . html::a($link, $label) . '
    '; + } + + /** + * Print link to an modules' methd. + * + * Before printing, check the privilege first. If no privilege, return fasle. Else, print the link, return true. + * + * @param string $module the module name + * @param string $method the method + * @param string $vars vars to be passed + * @param string $label the label of the link + * @param string $target the target of the link + * @param string $misc others + * @param bool $newline + * @static + * @access public + * @return bool + */ + public static function printLink($module, $method, $vars = '', $label, $target = '', $misc = '', $newline = true) + { + if(!common::hasPriv($module, $method)) return false; + echo html::a(helper::createLink($module, $method, $vars), $label, $target, $misc, $newline); + return true; + } + + /** + * Print icon of split line. + * + * @static + * @access public + * @return void + */ + public static function printDivider() + { + echo "    "; + } + + /** + * Print icon of comment. + * + * @param string $module + * @static + * @access public + * @return void + */ + public static function printCommentIcon($module) + { + global $lang; + + if(!common::hasPriv($module, 'edit')) return false; + echo ""; + echo html::a('#comment', ' ', '', "class='icon-green-common-comment' title='$lang->comment' onclick='setComment()'"); + echo ""; + } + + /** + * Print link icon. + * + * @param string $module + * @param string $method + * @param string $vars + * @param object $object + * @param string $type button|list + * @param string $icon + * @param string $target + * @static + * @access public + * @return void + */ + public static function printIcon($module, $method, $vars = '', $object = '', $type = 'button', $icon = '', $target = '') + { + global $app, $lang; + + /* Judge the $method of $module clickable or not, default is clickable. */ + $clickable = true; + if(is_object($object)) + { + if($app->getModuleName() != $module) $app->control->loadModel($module); + $modelClass = class_exists("ext{$module}Model") ? "ext{$module}Model" : $module . "Model"; + if(class_exists($modelClass) and is_callable(array($modelClass, 'isClickable'))) + { + $clickable = call_user_func_array(array($modelClass, 'isClickable'), array('object' => $object, 'method' => $method)); + } + } + + /* Set module and method, then create link to it. */ + if(strtolower($module) == 'testcase' and strtolower($method) == 'createbug') ($module = 'bug') and ($method = 'create'); + if(strtolower($module) == 'story' and strtolower($method) == 'createcase') ($module = 'testcase') and ($method = 'create'); + if(strtolower($module) == 'bug' and strtolower($method) == 'tostory') ($module = 'story') and ($method = 'create'); + if(strtolower($module) == 'bug' and strtolower($method) == 'createcase') ($module = 'testcase') and ($method = 'create'); + if(!common::hasPriv($module, $method)) return false; + $link = helper::createLink($module, $method, $vars); + + /* Set the icon title, try search the $method defination in $module's lang or $common's lang. */ + $title = $method; + if($method == 'create' and $icon == 'copy') $method = 'copy'; + if(isset($lang->$method) and is_string($lang->$method)) $title = $lang->$method; + if((isset($lang->$module->$method) or $app->loadLang($module)) and isset($lang->$module->$method)) + { + $title = $method == 'report' ? $lang->$module->$method->common : $lang->$module->$method; + } + if($icon == 'toStory') $title = $lang->bug->toStory; + if($icon == 'createBug') $title = $lang->testtask->createBug; + + /* set the class. */ + if(!$icon) $icon = $method; + if(strpos(',edit,copy,report,export,delete,', ",$icon,") !== false) $module = 'common'; + $color = $type == 'button' ? 'green' : ($clickable ? 'green' : 'gray'); + $extraClass = strpos(',export,customFields,runCase,results,', ",$method,") !== false ? $method : ''; + $class = $extraClass ? "icon-$color-$module-$icon $extraClass" : "icon-$color-$module-$icon"; + + /* Create the icon link. */ + if($clickable) + { + if($type == 'button') + { + echo ""; + echo html::a($link, ' ', $target, "class='$class' title='$title'", true); + if($method != 'edit' and $method != 'copy' and $method != 'delete') + { + echo html::a($link, $title, $target, "class='$extraClass'", true); + } + echo ""; + } + else + { + echo html::a($link, ' ', $target, "class='$class' title='$title'", false); + } + } + else + { + if($type == 'list') + { + echo " "; + } + } + } + + /** + * Print backLink and preLink and nextLink. + * + * @param string $backLink + * @param object $preAndNext + * @access public + * @return void + */ + public function printRPN($backLink, $preAndNext = '') + { + global $lang; + + echo ""; + echo html::a($backLink, ' ', '', "class='icon-goback' title={$lang->goback}"); + echo ""; + + if(isset($preAndNext->pre) and $preAndNext->pre) + { + $title = isset($preAndNext->pre->title) ? $preAndNext->pre->title : $preAndNext->pre->name; + $title = '#' . $preAndNext->pre->id . ' ' . $title; + echo ""; + echo html::a($this->inLink('view', "ID={$preAndNext->pre->id}"), ' ', '', "id='pre' class='icon-pre' title='{$title}'"); + echo ""; + } + if(isset($preAndNext->next) and $preAndNext->next) + { + $title = isset($preAndNext->next->title) ? $preAndNext->next->title : $preAndNext->next->name; + $title = '#' . $preAndNext->next->id . ' ' . $title; + echo ""; + echo html::a($this->inLink('view', "ID={$preAndNext->next->id}"), ' ', '', "id='next' class='icon-next' title='$title'"); + echo ""; + } + } + + /** + * Create changes of one object. + * + * @param mixed $old the old object + * @param mixed $new the new object + * @static + * @access public + * @return array + */ + public static function createChanges($old, $new) + { + global $config; + $changes = array(); + $magicQuote = get_magic_quotes_gpc(); + foreach($new as $key => $value) + { + if(strtolower($key) == 'lastediteddate') continue; + if(strtolower($key) == 'lasteditedby') continue; + if(strtolower($key) == 'assigneddate') continue; + if(strtolower($key) == 'editedby') continue; + if(strtolower($key) == 'editeddate') continue; + + if($magicQuote) $value = stripslashes($value); + if($value != $old->$key) + { + $diff = ''; + if(substr_count($value, "\n") > 1 or substr_count($old->$key, "\n") > 1 or strpos('name,title,desc,spec,steps,content,digest,verify', strtolower($key)) !== false) $diff = commonModel::diff($old->$key, $value); + $changes[] = array('field' => $key, 'old' => $old->$key, 'new' => $value, 'diff' => $diff); + } + } + return $changes; + } + + /** + * Get the full url of the system. + * + * @access public + * @return string + */ + public function getSysURL() + { + $httpType = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on') ? 'https' : 'http'; + $httpHost = $_SERVER['HTTP_HOST']; + return "$httpType://$httpHost"; + } + + /** + * Print the run info. + * + * @param mixed $startTime the start time. + * @access public + * @return void + */ + public function printRunInfo($startTime) + { + vprintf($this->lang->runInfo, $this->common->getRunInfo($startTime)); + } +} diff --git a/trunk/module/common/lang/en.php b/trunk/module/common/lang/en.php new file mode 100644 index 0000000000..38a4e519ed --- /dev/null +++ b/trunk/module/common/lang/en.php @@ -0,0 +1,304 @@ + + * @package ZenTaoPMS + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->arrow = '  '; +$lang->colon = '::'; +$lang->comma = ','; +$lang->dot = '.'; +$lang->at = ' at '; +$lang->downArrow = '↓'; + +$lang->ZenTaoPMS = 'ZenTaoPMS'; +$lang->welcome = "Welcome to『%s』{$lang->colon} {$lang->ZenTaoPMS}"; +$lang->myControl = "Dashboard"; +$lang->currentPos = 'Current'; +$lang->logout = 'Logout'; +$lang->login = 'Login'; +$lang->aboutZenTao = 'About'; +$lang->todayIs = '%s, '; +$lang->runInfo = "
    Time: %s ms, Memory: %s KB, Queries: %s.
    "; + +$lang->reset = 'Reset'; +$lang->edit = 'Edit'; +$lang->copy = 'Copy'; +$lang->delete = 'Delete'; +$lang->close = 'Close'; +$lang->link = 'Link'; +$lang->unlink = 'Unlink'; +$lang->import = 'Import'; +$lang->export = 'Export'; +$lang->setFileName = 'Filename:'; +$lang->activate = 'Activate'; +$lang->submitting = 'Saving...'; +$lang->save = 'Save'; +$lang->confirm = 'Confirm'; +$lang->preview = 'View'; +$lang->goback = 'Back'; +$lang->go = 'GO'; +$lang->more = 'More'; + +$lang->actions = 'Actions'; +$lang->comment = 'Comment'; +$lang->history = 'History'; +$lang->attatch = 'Attatch'; +$lang->reverse = '[Reverse]'; +$lang->switchDisplay= '[Toggle Show]'; +$lang->switchHelp = 'Toggle Help'; +$lang->addFiles = 'Add Files'; +$lang->files = 'Files '; +$lang->unfold = '+'; +$lang->fold = '-'; + +$lang->selectAll = 'Select All'; +$lang->selectReverse = 'Select Reverse'; +$lang->notFound = 'Sorry, the object not found.'; +$lang->showAll = '++ Show All ++'; +$lang->hideClosed = '-- Hide Closed--'; + +$lang->future = 'Future'; +$lang->year = 'Year'; +$lang->workingHour = 'Hour'; + +$lang->idAB = 'ID'; +$lang->priAB = 'P'; +$lang->statusAB = 'Status'; +$lang->openedByAB = 'Open'; +$lang->assignedToAB = 'To'; +$lang->typeAB = 'Type'; + +$lang->common->common = 'Common module'; + +/* The main menu. */ +$lang->menu->my = ' Dashboard|my|index'; +$lang->menu->product = 'Product|product|index'; +$lang->menu->project = 'Project|project|index'; +$lang->menu->qa = 'Test|qa|index'; +$lang->menu->doc = 'Doc|doc|index'; +$lang->menu->report = 'Report|report|index'; +$lang->menu->company = 'Company|company|index'; +$lang->menu->admin = 'Admin|admin|index'; + +/* The objects in the search box. */ +$lang->searchObjects['bug'] = 'Bug'; +$lang->searchObjects['story'] = 'Story'; +$lang->searchObjects['task'] = 'Task'; +$lang->searchObjects['testcase'] = 'Test Case'; +$lang->searchObjects['project'] = 'Project'; +$lang->searchObjects['product'] = 'Product'; +$lang->searchObjects['user'] = 'User'; +$lang->searchObjects['build'] = 'Build'; +$lang->searchObjects['release'] = 'Release'; +$lang->searchObjects['productplan'] = 'Plan'; +$lang->searchObjects['testtask'] = 'Test Task'; +$lang->searchObjects['doc'] = 'Doc'; +$lang->searchTips = 'Id here(ctrl+g)'; + +/* Encode list of impot. */ +$lang->importEncodeList['gbk'] = 'GBK'; +$lang->importEncodeList['big5'] = 'BIG5'; +$lang->importEncodeList['utf-8'] = 'UTF-8'; + +/* File type of export. */ +$lang->exportFileTypeList['csv'] = 'csv'; +$lang->exportFileTypeList['xml'] = 'xml'; +$lang->exportFileTypeList['html'] = 'html'; + +/* Support charset. */ +$lang->exportEncodeList['gbk'] = 'GBK'; +$lang->exportEncodeList['big5'] = 'BIG5'; +$lang->exportEncodeList['utf-8'] = 'UTF-8'; + +/* Themes. */ +$lang->themes['default'] = 'Default'; +$lang->themes['green'] = 'Green'; +$lang->themes['red'] = 'Red'; +$lang->themes['classblue'] = 'Blue'; + +/* Index mododule menu. */ +$lang->index->menu->product = 'Products|product|browse'; +$lang->index->menu->project = 'Projects|project|browse'; + +/* Dashboard menu. */ +$lang->my->menu->account = ' %s' . $lang->arrow; +$lang->my->menu->index = 'Index|my|index'; +$lang->my->menu->todo = array('link' => 'Todo|my|todo|', 'subModule' => 'todo'); +$lang->my->menu->task = 'Task|my|task|'; +$lang->my->menu->bug = 'Bug|my|bug|'; +$lang->my->menu->testtask = 'Test|my|testtask|'; +$lang->my->menu->story = 'Story|my|story|'; +$lang->my->menu->myProject = 'Project|my|project|'; +$lang->my->menu->dynamic = 'Dynamic|my|dynamic|'; +$lang->my->menu->profile = array('link' => 'Profile|my|profile|', 'alias' => 'editprofile'); +$lang->my->menu->changePassword = 'Change Password|my|changepassword|'; +$lang->todo->menu = $lang->my->menu; + +/* Product menu. */ +$lang->product->menu->list = '%s'; +$lang->product->menu->story = array('link' => 'Story|product|browse|productID=%s', 'subModule' => 'story'); +$lang->product->menu->dynamic = 'Dynamic|product|dynamic|productID=%s'; +$lang->product->menu->plan = array('link' => 'Plan|productplan|browse|productID=%s', 'subModule' => 'productplan'); +$lang->product->menu->release = array('link' => 'Release|release|browse|productID=%s', 'subModule' => 'release'); +$lang->product->menu->roadmap = 'Roadmap|product|roadmap|productID=%s'; +$lang->product->menu->doc = array('link' => 'Doc|product|doc|productID=%s', 'subModule' => 'doc'); +$lang->product->menu->view = array('link' => 'Info|product|view|productID=%s', 'alias' => 'edit'); +$lang->product->menu->module = 'Modules|tree|browse|productID=%s&view=story'; +$lang->product->menu->project = 'Projects|product|project|status=all&productID=%s'; +$lang->product->menu->order = 'Order|product|order|productID=%s'; +$lang->product->menu->create = array('link' => ' New|product|create', 'float' => 'right'); +$lang->product->menu->all = array('link' => ' All|product|index|locate=false', 'float' => 'right'); +$lang->story->menu = $lang->product->menu; +$lang->productplan->menu = $lang->product->menu; +$lang->release->menu = $lang->product->menu; + +/* Project menu. */ +$lang->project->menu->list = '%s'; +$lang->project->menu->task = array('link' => 'Task|project|task|projectID=%s', 'subModule' => 'task', 'alias' => 'grouptask,importtask'); +$lang->project->menu->story = array('link' => 'Story|project|story|projectID=%s', 'subModule' => 'story', 'alias' => 'linkstory'); +$lang->project->menu->bug = 'Bug|project|bug|projectID=%s'; +$lang->project->menu->dynamic = 'Dynamic|project|dynamic|projectID=%s'; +$lang->project->menu->build = array('link' => 'Build|project|build|projectID=%s', 'subModule' => 'build'); +$lang->project->menu->testtask = 'Testtask|project|testtask|projectID=%s'; +$lang->project->menu->team = array('link' => 'Team|project|team|projectID=%s', 'alias' => 'managemembers'); +$lang->project->menu->doc = array('link' => 'Doc|project|doc|porjectID=%s', 'subModule' => 'doc'); +$lang->project->menu->product = array('link' => 'Product|project|manageproducts|projectID=%s', 'alias' => 'edit'); +$lang->project->menu->view = 'Info|project|view|projectID=%s'; +$lang->project->menu->order = 'Order|project|order|projectID=%s'; +$lang->project->menu->create = array('link' => ' New|project|create', 'float' => 'right'); +$lang->project->menu->all = array('link' => ' Projects|project|index|locate=no', 'float' => 'right'); +$lang->task->menu = $lang->project->menu; +$lang->build->menu = $lang->project->menu; + +/* QA menu. */ +$lang->bug->menu->product = '%s'; +$lang->bug->menu->bug = array('link' => 'Bug|bug|browse|productID=%s', 'alias' => 'view,create,edit,resolve,close,activate,report', 'subModule' => 'tree'); +$lang->bug->menu->testcase = array('link' => 'Test Case|testcase|browse|productID=%s', 'alias' => 'view,create,edit'); +$lang->bug->menu->testtask = array('link' => 'Test Task|testtask|browse|productID=%s'); + +$lang->testcase->menu->product = '%s'; +$lang->testcase->menu->bug = array('link' => 'Bug|bug|browse|productID=%s'); +$lang->testcase->menu->testcase = array('link' => 'Test Case|testcase|browse|productID=%s', 'alias' => 'view,create,batchcreate,edit', 'subModule' => 'tree'); +$lang->testcase->menu->testtask = array('link' => 'Test Task|testtask|browse|productID=%s', 'alias' => 'view,create,edit,linkcase,cases'); +$lang->testtask->menu = $lang->testcase->menu; + +/* Doc menu. */ +$lang->doc->menu->list = '%s'; +$lang->doc->menu->browse = array('link' => 'Doc|doc|browse|libID=%s'); +$lang->doc->menu->edit = 'Edit Library|doc|editLib|libID=%s'; +$lang->doc->menu->module = 'Modules|tree|browse|libID=%s&viewType=doc'; +$lang->doc->menu->delete = array('link' => 'Delete Library|doc|deleteLib|libID=%s', 'target' => 'hiddenwin'); +$lang->doc->menu->create = array('link' => ' New Library|doc|createLib', 'float' => 'right'); + +/* Report menu. */ +$lang->report->menu->product = array('link' => 'Product|report|productinfo'); +$lang->report->menu->prj = array('link' => 'Project|report|projectdeviation'); +$lang->report->menu->test = array('link' => 'Test|report|bugsummary', 'alias' => 'bugassign'); +$lang->report->menu->staff = array('link' => 'Company|report|workload'); + +/* Company menu. */ +$lang->company->menu->name = '%s' . $lang->arrow; +$lang->company->menu->browseUser = array('link' => 'Users|company|browse', 'subModule' => 'user'); +$lang->company->menu->dept = array('link' => 'Department|dept|browse', 'subModule' => 'dept'); +$lang->company->menu->browseGroup = array('link' => 'Group|group|browse', 'subModule' => 'group'); +$lang->company->menu->edit = array('link' => 'Company|company|edit'); +$lang->company->menu->dynamic = 'Dynamic|company|dynamic|'; +$lang->company->menu->addGroup = array('link' => ' Add Group|group|create', 'float' => 'right'); +$lang->company->menu->addUser = array('link' => ' Add User|user|create|dept=%s&from=company', 'subModule' => 'user', 'float' => 'right'); +$lang->dept->menu = $lang->company->menu; +$lang->group->menu = $lang->company->menu; +$lang->user->menu = $lang->company->menu; + +/* Admin menu. */ +$lang->admin->menu->index = array('link' => 'Index|admin|index'); +$lang->admin->menu->extension = array('link' => 'Extension|extension|browse', 'subModule' => 'extension'); +$lang->admin->menu->editor = array('link' => 'Extension editor|editor|index', 'subModule' => 'editor'); +$lang->admin->menu->mail = array('link' => 'EmailSetting|mail|index', 'subModule' => 'mail'); +$lang->admin->menu->clearData = array('link' => 'ClearData|admin|cleardata'); +$lang->admin->menu->convert = array('link' => 'Import|convert|index', 'subModule' => 'convert'); +$lang->admin->menu->trashes = array('link' => 'Trash|action|trash', 'subModule' => 'action'); +$lang->convert->menu = $lang->admin->menu; +$lang->upgrade->menu = $lang->admin->menu; +$lang->action->menu = $lang->admin->menu; +$lang->extension->menu = $lang->admin->menu; +$lang->editor->menu = $lang->admin->menu; +$lang->mail->menu = $lang->admin->menu; + +/* Groups. */ +$lang->menugroup->release = 'product'; +$lang->menugroup->story = 'product'; +$lang->menugroup->productplan = 'product'; +$lang->menugroup->task = 'project'; +$lang->menugroup->build = 'project'; +$lang->menugroup->convert = 'admin'; +$lang->menugroup->upgrade = 'admin'; +$lang->menugroup->user = 'company'; +$lang->menugroup->group = 'company'; +$lang->menugroup->bug = 'qa'; +$lang->menugroup->testcase = 'qa'; +$lang->menugroup->testtask = 'qa'; +$lang->menugroup->people = 'company'; +$lang->menugroup->dept = 'company'; +$lang->menugroup->todo = 'my'; +$lang->menugroup->action = 'admin'; +$lang->menugroup->extension = 'admin'; +$lang->menugroup->editor = 'admin'; +$lang->menugroup->mail = 'admin'; + +/* Error info. */ +$lang->error->companyNotFound = "The domain %s does not exist."; +$lang->error->length = array("『%s』length should be『%s』", "『%s』length should between『%s』and 『%s』."); +$lang->error->reg = "『%s』should like『%s』"; +$lang->error->unique = "『%s』has『%s』already. If you are sure this record has been deleted, you can restore it in admin panel, trash page."; +$lang->error->gt = "『%s』must greater than『%s』."; +$lang->error->ge = "『%s』must greater than or equal『%s』."; +$lang->error->notempty = "『%s』can not be empty."; +$lang->error->empty = "『%s』 must be empty."; +$lang->error->equal = "『%s』must be『%s』."; +$lang->error->int = array("『%s』should be interger", "『%s』should between『%s-%s』."); +$lang->error->float = "『%s』should be a interger or float."; +$lang->error->email = "『%s』should be email."; +$lang->error->date = "『%s』should be date"; +$lang->error->account = "『%s』should be a valid account."; +$lang->error->passwordsame = "Two passwords must be the same"; +$lang->error->passwordrule = "Password should more than six letters."; +$lang->error->accessDenied = 'No purview'; + +/* Pager. */ +$lang->pager->noRecord = "No records yet."; +$lang->pager->digest = "%s records, %s per page, %s/%s "; +$lang->pager->first = "First"; +$lang->pager->pre = "Previous"; +$lang->pager->next = "Next"; +$lang->pager->last = "Last"; +$lang->pager->locate = "GO!"; + +$lang->zentaoSite = "Official Site"; +$lang->chinaScrum = "Scrum community "; +$lang->agileTraining = "Training "; +$lang->donate = "Donate "; +$lang->proVersion = "Try pro version! "; + +$lang->suhosinInfo = "Warming:data is too large! Please enlarge the setting of sohusin.post.max_vars and sohusin.request.max_vars in php.ini. Otherwise partial data can't be saved."; + +$lang->noResultsMatch = "No results match"; + +/* Date times. */ +define('DT_DATETIME1', 'Y-m-d H:i:s'); +define('DT_DATETIME2', 'y-m-d H:i'); +define('DT_MONTHTIME1', 'n/d H:i'); +define('DT_MONTHTIME2', 'F j, H:i'); +define('DT_DATE1', 'Y-m-d'); +define('DT_DATE2', 'Ymd'); +define('DT_DATE3', 'F j, Y '); +define('DT_DATE4', 'M j'); +define('DT_TIME1', 'H:i:s'); +define('DT_TIME2', 'H:i'); + +include (dirname(__FILE__) . '/menuOrder.php'); diff --git a/trunk/module/common/lang/menuOrder.php b/trunk/module/common/lang/menuOrder.php new file mode 100644 index 0000000000..944b01c859 --- /dev/null +++ b/trunk/module/common/lang/menuOrder.php @@ -0,0 +1,119 @@ +menuOrder[5] = 'my'; +$lang->menuOrder[10] = 'product'; +$lang->menuOrder[15] = 'project'; +$lang->menuOrder[20] = 'qa'; +$lang->menuOrder[25] = 'doc'; +$lang->menuOrder[30] = 'report'; +$lang->menuOrder[35] = 'company'; +$lang->menuOrder[40] = 'admin'; + +/* index menu order. */ +$lang->index->menuOrder[5] = 'product'; +$lang->index->menuOrder[10] = 'project'; + +/* my menu order. */ +$lang->my->menuOrder[5] = 'account'; +$lang->my->menuOrder[10] = 'index'; +$lang->my->menuOrder[15] = 'todo'; +$lang->my->menuOrder[20] = 'task'; +$lang->my->menuOrder[25] = 'bug'; +$lang->my->menuOrder[30] = 'testtask'; +$lang->my->menuOrder[35] = 'story'; +$lang->my->menuOrder[40] = 'myProject'; +$lang->my->menuOrder[45] = 'dynamic'; +$lang->my->menuOrder[50] = 'profile'; +$lang->my->menuOrder[55] = 'changePassword'; +$lang->todo->menuOrder = $lang->my->menuOrder; + +/* product menu order. */ +$lang->product->menuOrder[5] = 'story'; +$lang->product->menuOrder[10] = 'dynamic'; +$lang->product->menuOrder[15] = 'plan'; +$lang->product->menuOrder[20] = 'release'; +$lang->product->menuOrder[25] = 'roadmap'; +$lang->product->menuOrder[30] = 'doc'; +$lang->product->menuOrder[35] = 'project'; +$lang->product->menuOrder[40] = 'view'; +$lang->product->menuOrder[45] = 'module'; +$lang->product->menuOrder[50] = 'order'; +$lang->product->menuOrder[55] = 'create'; +$lang->product->menuOrder[60] = 'all'; +$lang->story->menuOrder = $lang->product->menuOrder; +$lang->productplan->menuOrder = $lang->product->menuOrder; +$lang->release->menuOrder = $lang->product->menuOrder; + +/* project menu order. */ +$lang->project->menuOrder[5] = 'task'; +$lang->project->menuOrder[10] = 'story'; +$lang->project->menuOrder[15] = 'bug'; +$lang->project->menuOrder[20] = 'build'; +$lang->project->menuOrder[25] = 'testtask'; +$lang->project->menuOrder[30] = 'burn'; +$lang->project->menuOrder[35] = 'team'; +$lang->project->menuOrder[40] = 'dynamic'; +$lang->project->menuOrder[45] = 'doc'; +$lang->project->menuOrder[50] = 'product'; +$lang->project->menuOrder[55] = 'linkstory'; +$lang->project->menuOrder[60] = 'view'; +$lang->project->menuOrder[65] = 'order'; +$lang->project->menuOrder[70] = 'create'; +$lang->project->menuOrder[75] = 'copy'; +$lang->project->menuOrder[80] = 'all'; +$lang->task->menuOrder = $lang->project->menuOrder; +$lang->build->menuOrder = $lang->project->menuOrder; + +/* bug menu order. */ +$lang->bug->menuOrder[0] = 'product'; +$lang->bug->menuOrder[5] = 'bug'; +$lang->bug->menuOrder[10] = 'testcase'; +$lang->bug->menuOrder[15] = 'testtask'; + +/* testcase menu order. */ +$lang->testcase->menuOrder[0] = 'product'; +$lang->testcase->menuOrder[5] = 'bug'; +$lang->testcase->menuOrder[10] = 'testcase'; +$lang->testcase->menuOrder[15] = 'testtask'; +$lang->testtask->menuOrder = $lang->testcase->menuOrder; + +/* doc menu order. */ +$lang->doc->menuOrder[5] = 'browse'; +$lang->doc->menuOrder[10] = 'edit'; +$lang->doc->menuOrder[15] = 'module'; +$lang->doc->menuOrder[20] = 'delete'; +$lang->doc->menuOrder[25] = 'create'; + +/* report menu order. */ +$lang->report->menuOrder[5] = 'product'; +$lang->report->menuOrder[10] = 'prj'; +$lang->report->menuOrder[15] = 'test'; +$lang->report->menuOrder[20] = 'staff'; + +/* company menu order. */ +$lang->company->menuOrder[0] = 'name'; +$lang->company->menuOrder[5] = 'browseUser'; +$lang->company->menuOrder[10] = 'dept'; +$lang->company->menuOrder[15] = 'browseGroup'; +$lang->company->menuOrder[20] = 'edit'; +$lang->company->menuOrder[25] = 'dynamic'; +$lang->company->menuOrder[30] = 'addGroup'; +$lang->company->menuOrder[35] = 'addUser'; +$lang->dept->menuOrder = $lang->company->menuOrder; +$lang->group->menuOrder = $lang->company->menuOrder; +$lang->user->menuOrder = $lang->company->menuOrder; + +/* admin menu order. */ +$lang->admin->menuOrder[5] = 'index'; +$lang->admin->menuOrder[10] = 'extension'; +$lang->admin->menuOrder[15] = 'editor'; +$lang->admin->menuOrder[20] = 'mail'; +$lang->admin->menuOrder[25] = 'clearData'; +$lang->admin->menuOrder[30] = 'convert'; +$lang->admin->menuOrder[35] = 'trashes'; +$lang->convert->menuOrder = $lang->admin->menuOrder; +$lang->upgrade->menuOrder = $lang->admin->menuOrder; +$lang->action->menuOrder = $lang->admin->menuOrder; +$lang->extension->menuOrder = $lang->admin->menuOrder; +$lang->editor->menuOrder = $lang->admin->menuOrder; +$lang->mail->menuOrder = $lang->admin->menuOrder; diff --git a/trunk/module/common/lang/zh-cn.php b/trunk/module/common/lang/zh-cn.php new file mode 100644 index 0000000000..cba4176298 --- /dev/null +++ b/trunk/module/common/lang/zh-cn.php @@ -0,0 +1,304 @@ + + * @package ZenTaoPMS + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->arrow = '  '; +$lang->colon = '::'; +$lang->comma = ','; +$lang->dot = '。'; +$lang->at = ' 于 '; +$lang->downArrow = '↓'; + +$lang->ZenTaoPMS = '禅道管理'; +$lang->welcome = "欢迎使用『%s』{$lang->colon} {$lang->ZenTaoPMS}"; +$lang->myControl = "我的地盘"; +$lang->currentPos = '当前位置:'; +$lang->logout = '退出'; +$lang->login = '登录'; +$lang->aboutZenTao = '关于'; +$lang->todayIs = '今天是%s,'; +$lang->runInfo = "
    时间: %s 毫秒, 内存: %s KB, 查询: %s.
    "; + +$lang->reset = '重填'; +$lang->edit = '编辑'; +$lang->copy = '复制'; +$lang->delete = '删除'; +$lang->close = '关闭'; +$lang->link = '关联'; +$lang->unlink = '移除'; +$lang->import = '导入'; +$lang->export = '导出'; +$lang->setFileName = '文件名:'; +$lang->activate = '激活'; +$lang->submitting = '稍候...'; +$lang->save = '保存'; +$lang->confirm = '确认'; +$lang->preview = '查看'; +$lang->goback = '返回'; +$lang->go = 'GO'; +$lang->more = '更多'; + +$lang->actions = '操作'; +$lang->comment = '备注'; +$lang->history = '历史记录'; +$lang->attatch = '附件'; +$lang->reverse = '[切换顺序]'; +$lang->switchDisplay= '[切换显示]'; +$lang->switchHelp = '切换帮助'; +$lang->addFiles = '上传了附件 '; +$lang->files = '附件 '; +$lang->unfold = '+'; +$lang->fold = '-'; + +$lang->selectAll = '全选'; +$lang->selectReverse = '反选'; +$lang->notFound = '抱歉,您访问的对象并不存在!'; +$lang->showAll = '++ 全部显示 ++'; +$lang->hideClosed = '-- 隐藏已结束 --'; + +$lang->future = '未来'; +$lang->year = '年'; +$lang->workingHour = '工时'; + +$lang->idAB = 'ID'; +$lang->priAB = 'P'; +$lang->statusAB = '状态'; +$lang->openedByAB = '创建'; +$lang->assignedToAB = '指派'; +$lang->typeAB = '类型'; + +$lang->common->common = '公有模块'; + +/* 主导航菜单。*/ +$lang->menu->my = ' 我的地盘|my|index'; +$lang->menu->product = '产品视图|product|index'; +$lang->menu->project = '项目视图|project|index'; +$lang->menu->qa = '测试视图|qa|index'; +$lang->menu->doc = '文档视图|doc|index'; +$lang->menu->report = '统计视图|report|index'; +$lang->menu->company = '组织视图|company|index'; +$lang->menu->admin = '后台管理|admin|index'; + +/* 查询条中可以选择的对象列表。*/ +$lang->searchObjects['bug'] = 'B:Bug'; +$lang->searchObjects['story'] = 'S:需求'; +$lang->searchObjects['task'] = 'T:任务'; +$lang->searchObjects['testcase'] = 'C:用例'; +$lang->searchObjects['project'] = 'P:项目'; +$lang->searchObjects['product'] = 'P:产品'; +$lang->searchObjects['user'] = 'U:用户'; +$lang->searchObjects['build'] = 'B:版本'; +$lang->searchObjects['release'] = 'R:发布'; +$lang->searchObjects['productplan'] = 'P:产品计划'; +$lang->searchObjects['testtask'] = 'T:测试任务'; +$lang->searchObjects['doc'] = 'D:文档'; +$lang->searchTips = '编号(ctrl+g)'; + +/* 导入支持的编码格式。*/ +$lang->importEncodeList['gbk'] = 'GBK'; +$lang->importEncodeList['big5'] = 'BIG5'; +$lang->importEncodeList['utf-8'] = 'UTF-8'; + +/* 导出文件的类型列表。*/ +$lang->exportFileTypeList['csv'] = 'csv'; +$lang->exportFileTypeList['xml'] = 'xml'; +$lang->exportFileTypeList['html'] = 'html'; + +/* 支持的编码格式。*/ +$lang->exportEncodeList['gbk'] = 'GBK'; +$lang->exportEncodeList['big5'] = 'BIG5'; +$lang->exportEncodeList['utf-8'] = 'UTF-8'; + +/* 风格列表。*/ +$lang->themes['default'] = '默认'; +$lang->themes['green'] = '绿色'; +$lang->themes['red'] = '红色'; +$lang->themes['classblue'] = '经典蓝'; + +/* 首页菜单设置。*/ +$lang->index->menu->product = '浏览产品|product|browse'; +$lang->index->menu->project = '浏览项目|project|browse'; + +/* 我的地盘菜单设置。*/ +$lang->my->menu->account = ' %s' . $lang->arrow; +$lang->my->menu->index = '首页|my|index'; +$lang->my->menu->todo = array('link' => '我的TODO|my|todo|', 'subModule' => 'todo'); +$lang->my->menu->task = '我的任务|my|task|'; +$lang->my->menu->bug = '我的Bug|my|bug|'; +$lang->my->menu->testtask = '我的测试|my|testtask|'; +$lang->my->menu->story = '我的需求|my|story|'; +$lang->my->menu->myProject = '我的项目|my|project|'; +$lang->my->menu->dynamic = '我的动态|my|dynamic|'; +$lang->my->menu->profile = array('link' => '我的档案|my|profile|', 'alias' => 'editprofile'); +$lang->my->menu->changePassword = '修改密码|my|changepassword|'; +$lang->todo->menu = $lang->my->menu; + +/* 产品视图设置。*/ +$lang->product->menu->list = '%s'; +$lang->product->menu->story = array('link' => '需求|product|browse|productID=%s', 'subModule' => 'story'); +$lang->product->menu->dynamic = '动态|product|dynamic|productID=%s'; +$lang->product->menu->plan = array('link' => '计划|productplan|browse|productID=%s', 'subModule' => 'productplan'); +$lang->product->menu->release = array('link' => '发布|release|browse|productID=%s', 'subModule' => 'release'); +$lang->product->menu->roadmap = '路线图|product|roadmap|productID=%s'; +$lang->product->menu->doc = array('link' => '文档|product|doc|productID=%s', 'subModule' => 'doc'); +$lang->product->menu->view = array('link' => '概况|product|view|productID=%s', 'alias' => 'edit'); +$lang->product->menu->module = '模块|tree|browse|productID=%s&view=story'; +$lang->product->menu->project = '项目列表|product|project|status=all&productID=%s'; +$lang->product->menu->order = '排序|product|order|productID=%s'; +$lang->product->menu->create = array('link' => ' 新增产品|product|create', 'float' => 'right'); +$lang->product->menu->all = array('link' => ' 所有产品|product|index|locate=false', 'float' => 'right'); +$lang->story->menu = $lang->product->menu; +$lang->productplan->menu = $lang->product->menu; +$lang->release->menu = $lang->product->menu; + +/* 项目视图菜单设置。*/ +$lang->project->menu->list = '%s'; +$lang->project->menu->task = array('link' => '任务|project|task|projectID=%s', 'subModule' => 'task', 'alias' => 'grouptask,importtask,burn'); +$lang->project->menu->story = array('link' => '需求|project|story|projectID=%s', 'subModule' => 'story', 'alias' => 'linkstory'); +$lang->project->menu->bug = 'Bug|project|bug|projectID=%s'; +$lang->project->menu->dynamic = '动态|project|dynamic|projectID=%s'; +$lang->project->menu->build = array('link' => '版本|project|build|projectID=%s', 'subModule' => 'build'); +$lang->project->menu->testtask = '测试申请|project|testtask|projectID=%s'; +$lang->project->menu->team = array('link' => '团队|project|team|projectID=%s', 'alias' => 'managemembers'); +$lang->project->menu->doc = array('link' => '文档|project|doc|porjectID=%s', 'subModule' => 'doc'); +$lang->project->menu->product = '产品|project|manageproducts|projectID=%s'; +$lang->project->menu->view = array('link' => '概况|project|view|projectID=%s', 'alias' => 'edit'); +$lang->project->menu->order = '排序|project|order|projectID=%s'; +$lang->project->menu->create = array('link' => ' 新增项目|project|create', 'float' => 'right'); +$lang->project->menu->all = array('link' => ' 所有项目|project|index|locate=false', 'float' => 'right'); +$lang->task->menu = $lang->project->menu; +$lang->build->menu = $lang->project->menu; + +/* QA视图菜单设置。*/ +$lang->bug->menu->product = '%s'; +$lang->bug->menu->bug = array('link' => '缺陷管理|bug|browse|productID=%s', 'alias' => 'view,create,edit,resolve,close,activate,report', 'subModule' => 'tree'); +$lang->bug->menu->testcase = array('link' => '用例管理|testcase|browse|productID=%s', 'alias' => 'view,create,edit'); +$lang->bug->menu->testtask = array('link' => '测试任务|testtask|browse|productID=%s'); + +$lang->testcase->menu->product = '%s'; +$lang->testcase->menu->bug = array('link' => '缺陷管理|bug|browse|productID=%s'); +$lang->testcase->menu->testcase = array('link' => '用例管理|testcase|browse|productID=%s', 'alias' => 'view,create,batchcreate,edit', 'subModule' => 'tree'); +$lang->testcase->menu->testtask = array('link' => '测试任务|testtask|browse|productID=%s', 'alias' => 'view,create,edit,linkcase,cases'); +$lang->testtask->menu = $lang->testcase->menu; + +/* 文档视图菜单设置。*/ +$lang->doc->menu->list = '%s'; +$lang->doc->menu->browse = array('link' => '文档列表|doc|browse|libID=%s'); +$lang->doc->menu->edit = '编辑文档库|doc|editLib|libID=%s'; +$lang->doc->menu->module = '维护模块|tree|browse|libID=%s&viewType=doc'; +$lang->doc->menu->delete = array('link' => '删除文档库|doc|deleteLib|libID=%s', 'target' => 'hiddenwin'); +$lang->doc->menu->create = array('link' => ' 新增文档库|doc|createLib', 'float' => 'right'); + +/* 统计视图菜单设置。*/ +$lang->report->menu->product = array('link' => '产品|report|productinfo'); +$lang->report->menu->prj = array('link' => '项目|report|projectdeviation'); +$lang->report->menu->test = array('link' => '测试|report|bugsummary', 'alias' => 'bugassign'); +$lang->report->menu->staff = array('link' => '组织|report|workload'); + +/* 组织结构视图菜单设置。*/ +$lang->company->menu->name = '%s' . $lang->arrow; +$lang->company->menu->browseUser = array('link' => '用户列表|company|browse', 'subModule' => 'user'); +$lang->company->menu->dept = array('link' => '部门维护|dept|browse', 'subModule' => 'dept'); +$lang->company->menu->browseGroup = array('link' => '权限分组|group|browse', 'subModule' => 'group'); +$lang->company->menu->edit = array('link' => '公司管理|company|edit'); +$lang->company->menu->dynamic = '组织动态|company|dynamic|'; +$lang->company->menu->addGroup = array('link' => ' 添加分组|group|create', 'float' => 'right'); +$lang->company->menu->addUser = array('link' => ' 添加用户|user|create|dept=%s', 'subModule' => 'user', 'float' => 'right'); +$lang->dept->menu = $lang->company->menu; +$lang->group->menu = $lang->company->menu; +$lang->user->menu = $lang->company->menu; + +/* 后台管理菜单设置。*/ +$lang->admin->menu->index = array('link' => '首页|admin|index'); +$lang->admin->menu->extension = array('link' => '插件管理|extension|browse', 'subModule' => 'extension'); +$lang->admin->menu->editor = array('link' => '扩展编辑器|editor|index', 'subModule' => 'editor'); +$lang->admin->menu->mail = array('link' => '发信配置|mail|index', 'subModule' => 'mail'); +$lang->admin->menu->clearData = array('link' => '清除数据|admin|cleardata'); +$lang->admin->menu->convert = array('link' => '从其他系统导入|convert|index', 'subModule' => 'convert'); +$lang->admin->menu->trashes = array('link' => '回收站|action|trash', 'subModule' => 'action'); +$lang->convert->menu = $lang->admin->menu; +$lang->upgrade->menu = $lang->admin->menu; +$lang->action->menu = $lang->admin->menu; +$lang->extension->menu = $lang->admin->menu; +$lang->editor->menu = $lang->admin->menu; +$lang->mail->menu = $lang->admin->menu; + +/* 菜单分组。*/ +$lang->menugroup->release = 'product'; +$lang->menugroup->story = 'product'; +$lang->menugroup->productplan = 'product'; +$lang->menugroup->task = 'project'; +$lang->menugroup->build = 'project'; +$lang->menugroup->convert = 'admin'; +$lang->menugroup->upgrade = 'admin'; +$lang->menugroup->user = 'company'; +$lang->menugroup->group = 'company'; +$lang->menugroup->bug = 'qa'; +$lang->menugroup->testcase = 'qa'; +$lang->menugroup->testtask = 'qa'; +$lang->menugroup->people = 'company'; +$lang->menugroup->dept = 'company'; +$lang->menugroup->todo = 'my'; +$lang->menugroup->action = 'admin'; +$lang->menugroup->extension = 'admin'; +$lang->menugroup->editor = 'admin'; +$lang->menugroup->mail = 'admin'; + +/* 错误提示信息。*/ +$lang->error->companyNotFound = "您访问的域名 %s 没有对应的公司。"; +$lang->error->length = array("『%s』长度错误,应当为『%s』", "『%s』长度应当不超过『%s』,且不小于『%s』。"); +$lang->error->reg = "『%s』不符合格式,应当为:『%s』。"; +$lang->error->unique = "『%s』已经有『%s』这条记录了。如果您确定该记录已删除,请到后台管理-回收站还原。。"; +$lang->error->gt = "『%s』应当大于『%s』。"; +$lang->error->ge = "『%s』应当不小于『%s』。"; +$lang->error->notempty = "『%s』不能为空。"; +$lang->error->empty = "『%s』必须为空。"; +$lang->error->equal = "『%s』必须为『%s』。"; +$lang->error->int = array("『%s』应当是数字。", "『%s』应当介于『%s-%s』之间。"); +$lang->error->float = "『%s』应当是数字,可以是小数。"; +$lang->error->email = "『%s』应当为合法的EMAIL。"; +$lang->error->date = "『%s』应当为合法的日期。"; +$lang->error->account = "『%s』应当为合法的用户名。"; +$lang->error->passwordsame = "两次密码应当相等。"; +$lang->error->passwordrule = "密码应该符合规则,长度至少为六位。"; +$lang->error->accessDenied = '您没有访问权限'; + +/* 分页信息。*/ +$lang->pager->noRecord = "暂时没有记录"; +$lang->pager->digest = "共%s条记录,每页 %s条,%s/%s "; +$lang->pager->first = "首页"; +$lang->pager->pre = "上页"; +$lang->pager->next = "下页"; +$lang->pager->last = "末页"; +$lang->pager->locate = "GO!"; + +$lang->zentaoSite = "官方网站"; +$lang->chinaScrum = "Scrum社区 "; +$lang->agileTraining = "培训 "; +$lang->donate = "捐赠 "; +$lang->proVersion = "购买专业版(特惠)! "; + +$lang->suhosinInfo = "警告:数据太多,请在php.ini中修改sohusin.post.max_varssohusin.request.max_vars(设置更大的数)。 保存并重新启动apache,否则会造成部分数据无法保存。"; + +$lang->noResultsMatch = "没有匹配结果"; + +/* 时间格式设置。*/ +define('DT_DATETIME1', 'Y-m-d H:i:s'); +define('DT_DATETIME2', 'y-m-d H:i'); +define('DT_MONTHTIME1', 'n/d H:i'); +define('DT_MONTHTIME2', 'n月d日 H:i'); +define('DT_DATE1', 'Y-m-d'); +define('DT_DATE2', 'Ymd'); +define('DT_DATE3', 'Y年m月d日'); +define('DT_DATE4', 'n月j日'); +define('DT_TIME1', 'H:i:s'); +define('DT_TIME2', 'H:i'); + +include (dirname(__FILE__) . '/menuOrder.php'); diff --git a/trunk/module/common/lang/zh-tw.php b/trunk/module/common/lang/zh-tw.php new file mode 100644 index 0000000000..93f36e15e0 --- /dev/null +++ b/trunk/module/common/lang/zh-tw.php @@ -0,0 +1,304 @@ + + * @package ZenTaoPMS + * @version $Id: zh-tw.php 3485 2012-09-02 06:38:01Z wwccss $ + * @link http://www.zentao.net + */ +$lang->arrow = '  '; +$lang->colon = '::'; +$lang->comma = ','; +$lang->dot = '。'; +$lang->at = ' 于 '; +$lang->downArrow = '↓'; + +$lang->ZenTaoPMS = '禪道管理'; +$lang->welcome = "歡迎使用『%s』{$lang->colon} {$lang->ZenTaoPMS}"; +$lang->myControl = "我的地盤"; +$lang->currentPos = '當前位置:'; +$lang->logout = '退出'; +$lang->login = '登錄'; +$lang->aboutZenTao = '關於'; +$lang->todayIs = '今天是%s,'; +$lang->runInfo = "
    時間: %s 毫秒, 內存: %s KB, 查詢: %s.
    "; + +$lang->reset = '重填'; +$lang->edit = '編輯'; +$lang->copy = '複製'; +$lang->delete = '刪除'; +$lang->close = '關閉'; +$lang->link = '關聯'; +$lang->unlink = '移除'; +$lang->import = '導入'; +$lang->export = '導出'; +$lang->setFileName = '檔案名:'; +$lang->activate = '激活'; +$lang->submitting = '稍候...'; +$lang->save = '保存'; +$lang->confirm = '確認'; +$lang->preview = '查看'; +$lang->goback = '返回'; +$lang->go = 'GO'; +$lang->more = '更多'; + +$lang->actions = '操作'; +$lang->comment = '備註'; +$lang->history = '歷史記錄'; +$lang->attatch = '附件'; +$lang->reverse = '[切換順序]'; +$lang->switchDisplay= '[切換顯示]'; +$lang->switchHelp = '切換幫助'; +$lang->addFiles = '上傳了附件 '; +$lang->files = '附件 '; +$lang->unfold = '+'; +$lang->fold = '-'; + +$lang->selectAll = '全選'; +$lang->selectReverse = '反選'; +$lang->notFound = '抱歉,您訪問的對象並不存在!'; +$lang->showAll = '++ 全部顯示 ++'; +$lang->hideClosed = '-- 隱藏已結束 --'; + +$lang->future = '未來'; +$lang->year = '年'; +$lang->workingHour = '工時'; + +$lang->idAB = 'ID'; +$lang->priAB = 'P'; +$lang->statusAB = '狀態'; +$lang->openedByAB = '創建'; +$lang->assignedToAB = '指派'; +$lang->typeAB = '類型'; + +$lang->common->common = '公有模組'; + +/* 主導航菜單。*/ +$lang->menu->my = ' 我的地盤|my|index'; +$lang->menu->product = '產品視圖|product|index'; +$lang->menu->project = '項目視圖|project|index'; +$lang->menu->qa = '測試視圖|qa|index'; +$lang->menu->doc = '文檔視圖|doc|index'; +$lang->menu->report = '統計視圖|report|index'; +$lang->menu->company = '組織視圖|company|index'; +$lang->menu->admin = '後台管理|admin|index'; + +/* 查詢條中可以選擇的對象列表。*/ +$lang->searchObjects['bug'] = 'B:Bug'; +$lang->searchObjects['story'] = 'S:需求'; +$lang->searchObjects['task'] = 'T:任務'; +$lang->searchObjects['testcase'] = 'C:用例'; +$lang->searchObjects['project'] = 'P:項目'; +$lang->searchObjects['product'] = 'P:產品'; +$lang->searchObjects['user'] = 'U:用戶'; +$lang->searchObjects['build'] = 'B:版本'; +$lang->searchObjects['release'] = 'R:發佈'; +$lang->searchObjects['productplan'] = 'P:產品計劃'; +$lang->searchObjects['testtask'] = 'T:測試任務'; +$lang->searchObjects['doc'] = 'D:文檔'; +$lang->searchTips = '編號(ctrl+g)'; + +/* 導入支持的編碼格式。*/ +$lang->importEncodeList['gbk'] = 'GBK'; +$lang->importEncodeList['big5'] = 'BIG5'; +$lang->importEncodeList['utf-8'] = 'UTF-8'; + +/* 導出檔案的類型列表。*/ +$lang->exportFileTypeList['csv'] = 'csv'; +$lang->exportFileTypeList['xml'] = 'xml'; +$lang->exportFileTypeList['html'] = 'html'; + +/* 支持的編碼格式。*/ +$lang->exportEncodeList['gbk'] = 'GBK'; +$lang->exportEncodeList['big5'] = 'BIG5'; +$lang->exportEncodeList['utf-8'] = 'UTF-8'; + +/* 風格列表。*/ +$lang->themes['default'] = '預設'; +$lang->themes['green'] = '綠色'; +$lang->themes['red'] = '紅色'; +$lang->themes['classblue'] = '經典藍'; + +/* 首頁菜單設置。*/ +$lang->index->menu->product = '瀏覽產品|product|browse'; +$lang->index->menu->project = '瀏覽項目|project|browse'; + +/* 我的地盤菜單設置。*/ +$lang->my->menu->account = ' %s' . $lang->arrow; +$lang->my->menu->index = '首頁|my|index'; +$lang->my->menu->todo = array('link' => '我的TODO|my|todo|', 'subModule' => 'todo'); +$lang->my->menu->task = '我的任務|my|task|'; +$lang->my->menu->bug = '我的Bug|my|bug|'; +$lang->my->menu->testtask = '我的測試|my|testtask|'; +$lang->my->menu->story = '我的需求|my|story|'; +$lang->my->menu->myProject = '我的項目|my|project|'; +$lang->my->menu->dynamic = '我的動態|my|dynamic|'; +$lang->my->menu->profile = array('link' => '我的檔案|my|profile|', 'alias' => 'editprofile'); +$lang->my->menu->changePassword = '修改密碼|my|changepassword|'; +$lang->todo->menu = $lang->my->menu; + +/* 產品視圖設置。*/ +$lang->product->menu->list = '%s'; +$lang->product->menu->story = array('link' => '需求|product|browse|productID=%s', 'subModule' => 'story'); +$lang->product->menu->dynamic = '動態|product|dynamic|productID=%s'; +$lang->product->menu->plan = array('link' => '計劃|productplan|browse|productID=%s', 'subModule' => 'productplan'); +$lang->product->menu->release = array('link' => '發佈|release|browse|productID=%s', 'subModule' => 'release'); +$lang->product->menu->roadmap = '路線圖|product|roadmap|productID=%s'; +$lang->product->menu->doc = array('link' => '文檔|product|doc|productID=%s', 'subModule' => 'doc'); +$lang->product->menu->view = array('link' => '概況|product|view|productID=%s', 'alias' => 'edit'); +$lang->product->menu->module = '模組|tree|browse|productID=%s&view=story'; +$lang->product->menu->project = '項目列表|product|project|status=all&productID=%s'; +$lang->product->menu->order = '排序|product|order|productID=%s'; +$lang->product->menu->create = array('link' => ' 新增產品|product|create', 'float' => 'right'); +$lang->product->menu->all = array('link' => ' 所有產品|product|index|locate=false', 'float' => 'right'); +$lang->story->menu = $lang->product->menu; +$lang->productplan->menu = $lang->product->menu; +$lang->release->menu = $lang->product->menu; + +/* 項目視圖菜單設置。*/ +$lang->project->menu->list = '%s'; +$lang->project->menu->task = array('link' => '任務|project|task|projectID=%s', 'subModule' => 'task', 'alias' => 'grouptask,importtask,burn'); +$lang->project->menu->story = array('link' => '需求|project|story|projectID=%s', 'subModule' => 'story', 'alias' => 'linkstory'); +$lang->project->menu->bug = 'Bug|project|bug|projectID=%s'; +$lang->project->menu->dynamic = '動態|project|dynamic|projectID=%s'; +$lang->project->menu->build = array('link' => '版本|project|build|projectID=%s', 'subModule' => 'build'); +$lang->project->menu->testtask = '測試申請|project|testtask|projectID=%s'; +$lang->project->menu->team = array('link' => '團隊|project|team|projectID=%s', 'alias' => 'managemembers'); +$lang->project->menu->doc = array('link' => '文檔|project|doc|porjectID=%s', 'subModule' => 'doc'); +$lang->project->menu->product = '產品|project|manageproducts|projectID=%s'; +$lang->project->menu->view = array('link' => '概況|project|view|projectID=%s', 'alias' => 'edit'); +$lang->project->menu->order = '排序|project|order|projectID=%s'; +$lang->project->menu->create = array('link' => ' 新增項目|project|create', 'float' => 'right'); +$lang->project->menu->all = array('link' => ' 所有項目|project|index|locate=false', 'float' => 'right'); +$lang->task->menu = $lang->project->menu; +$lang->build->menu = $lang->project->menu; + +/* QA視圖菜單設置。*/ +$lang->bug->menu->product = '%s'; +$lang->bug->menu->bug = array('link' => '缺陷管理|bug|browse|productID=%s', 'alias' => 'view,create,edit,resolve,close,activate,report', 'subModule' => 'tree'); +$lang->bug->menu->testcase = array('link' => '用例管理|testcase|browse|productID=%s', 'alias' => 'view,create,edit'); +$lang->bug->menu->testtask = array('link' => '測試任務|testtask|browse|productID=%s'); + +$lang->testcase->menu->product = '%s'; +$lang->testcase->menu->bug = array('link' => '缺陷管理|bug|browse|productID=%s'); +$lang->testcase->menu->testcase = array('link' => '用例管理|testcase|browse|productID=%s', 'alias' => 'view,create,batchcreate,edit', 'subModule' => 'tree'); +$lang->testcase->menu->testtask = array('link' => '測試任務|testtask|browse|productID=%s', 'alias' => 'view,create,edit,linkcase,cases'); +$lang->testtask->menu = $lang->testcase->menu; + +/* 文檔視圖菜單設置。*/ +$lang->doc->menu->list = '%s'; +$lang->doc->menu->browse = array('link' => '文檔列表|doc|browse|libID=%s'); +$lang->doc->menu->edit = '編輯文檔庫|doc|editLib|libID=%s'; +$lang->doc->menu->module = '維護模組|tree|browse|libID=%s&viewType=doc'; +$lang->doc->menu->delete = array('link' => '刪除文檔庫|doc|deleteLib|libID=%s', 'target' => 'hiddenwin'); +$lang->doc->menu->create = array('link' => ' 新增文檔庫|doc|createLib', 'float' => 'right'); + +/* 統計視圖菜單設置。*/ +$lang->report->menu->product = array('link' => '產品|report|productinfo'); +$lang->report->menu->prj = array('link' => '項目|report|projectdeviation'); +$lang->report->menu->test = array('link' => '測試|report|bugsummary', 'alias' => 'bugassign'); +$lang->report->menu->staff = array('link' => '組織|report|workload'); + +/* 組織結構視圖菜單設置。*/ +$lang->company->menu->name = '%s' . $lang->arrow; +$lang->company->menu->browseUser = array('link' => '用戶列表|company|browse', 'subModule' => 'user'); +$lang->company->menu->dept = array('link' => '部門維護|dept|browse', 'subModule' => 'dept'); +$lang->company->menu->browseGroup = array('link' => '權限分組|group|browse', 'subModule' => 'group'); +$lang->company->menu->edit = array('link' => '公司管理|company|edit'); +$lang->company->menu->dynamic = '組織動態|company|dynamic|'; +$lang->company->menu->addGroup = array('link' => ' 添加分組|group|create', 'float' => 'right'); +$lang->company->menu->addUser = array('link' => ' 添加用戶|user|create|dept=%s', 'subModule' => 'user', 'float' => 'right'); +$lang->dept->menu = $lang->company->menu; +$lang->group->menu = $lang->company->menu; +$lang->user->menu = $lang->company->menu; + +/* 後台管理菜單設置。*/ +$lang->admin->menu->index = array('link' => '首頁|admin|index'); +$lang->admin->menu->extension = array('link' => '插件管理|extension|browse', 'subModule' => 'extension'); +$lang->admin->menu->editor = array('link' => '擴展編輯器|editor|index', 'subModule' => 'editor'); +$lang->admin->menu->mail = array('link' => '發信配置|mail|index', 'subModule' => 'mail'); +$lang->admin->menu->clearData = array('link' => '清除數據|admin|cleardata'); +$lang->admin->menu->convert = array('link' => '從其他系統導入|convert|index', 'subModule' => 'convert'); +$lang->admin->menu->trashes = array('link' => '資源回收筒|action|trash', 'subModule' => 'action'); +$lang->convert->menu = $lang->admin->menu; +$lang->upgrade->menu = $lang->admin->menu; +$lang->action->menu = $lang->admin->menu; +$lang->extension->menu = $lang->admin->menu; +$lang->editor->menu = $lang->admin->menu; +$lang->mail->menu = $lang->admin->menu; + +/* 菜單分組。*/ +$lang->menugroup->release = 'product'; +$lang->menugroup->story = 'product'; +$lang->menugroup->productplan = 'product'; +$lang->menugroup->task = 'project'; +$lang->menugroup->build = 'project'; +$lang->menugroup->convert = 'admin'; +$lang->menugroup->upgrade = 'admin'; +$lang->menugroup->user = 'company'; +$lang->menugroup->group = 'company'; +$lang->menugroup->bug = 'qa'; +$lang->menugroup->testcase = 'qa'; +$lang->menugroup->testtask = 'qa'; +$lang->menugroup->people = 'company'; +$lang->menugroup->dept = 'company'; +$lang->menugroup->todo = 'my'; +$lang->menugroup->action = 'admin'; +$lang->menugroup->extension = 'admin'; +$lang->menugroup->editor = 'admin'; +$lang->menugroup->mail = 'admin'; + +/* 錯誤提示信息。*/ +$lang->error->companyNotFound = "您訪問的域名 %s 沒有對應的公司。"; +$lang->error->length = array("『%s』長度錯誤,應當為『%s』", "『%s』長度應當不超過『%s』,且不小於『%s』。"); +$lang->error->reg = "『%s』不符合格式,應當為:『%s』。"; +$lang->error->unique = "『%s』已經有『%s』這條記錄了。"; +$lang->error->gt = "『%s』應當大於『%s』。"; +$lang->error->ge = "『%s』應當不小於『%s』。"; +$lang->error->notempty = "『%s』不能為空。"; +$lang->error->empty = "『%s』必須為空。"; +$lang->error->equal = "『%s』必須為『%s』。"; +$lang->error->int = array("『%s』應當是數字。", "『%s』應當介於『%s-%s』之間。"); +$lang->error->float = "『%s』應當是數字,可以是小數。"; +$lang->error->email = "『%s』應當為合法的EMAIL。"; +$lang->error->date = "『%s』應當為合法的日期。"; +$lang->error->account = "『%s』應當為合法的用戶名。"; +$lang->error->passwordsame = "兩次密碼應當相等。"; +$lang->error->passwordrule = "密碼應該符合規則,長度至少為六位。"; +$lang->error->accessDenied = '您沒有訪問權限'; + +/* 分頁信息。*/ +$lang->pager->noRecord = "暫時沒有記錄"; +$lang->pager->digest = "共%s條記錄,每頁 %s條,%s/%s "; +$lang->pager->first = "首頁"; +$lang->pager->pre = "上頁"; +$lang->pager->next = "下頁"; +$lang->pager->last = "末頁"; +$lang->pager->locate = "GO!"; + +$lang->zentaoSite = "官方網站"; +$lang->chinaScrum = "Scrum社區 "; +$lang->agileTraining = "培訓 "; +$lang->donate = "捐贈 "; +$lang->proVersion = "購買專業版(特惠)! "; + +$lang->suhosinInfo = "警告:數據太多,請在php.ini中修改sohusin.post.max_varssohusin.request.max_vars(設置更大的數)。 保存並重新啟動apache,否則會造成部分數據無法保存。"; + +$lang->noResultsMatch = "沒有匹配結果"; + +/* 時間格式設置。*/ +define('DT_DATETIME1', 'Y-m-d H:i:s'); +define('DT_DATETIME2', 'y-m-d H:i'); +define('DT_MONTHTIME1', 'n/d H:i'); +define('DT_MONTHTIME2', 'n月d日 H:i'); +define('DT_DATE1', 'Y-m-d'); +define('DT_DATE2', 'Ymd'); +define('DT_DATE3', 'Y年m月d日'); +define('DT_DATE4', 'n月j日'); +define('DT_TIME1', 'H:i:s'); +define('DT_TIME2', 'H:i'); + +include (dirname(__FILE__) . '/menuOrder.php'); diff --git a/trunk/module/common/model.php b/trunk/module/common/model.php new file mode 100644 index 0000000000..b5b31f444a --- /dev/null +++ b/trunk/module/common/model.php @@ -0,0 +1,573 @@ + + * @package common + * @version $Id$ + * @link http://www.zentao.net + */ +class commonModel extends model +{ + /** + * Start the session. + * + * @access public + * @return void + */ + public function startSession() + { + session_name($this->config->sessionVar); + if(isset($_GET[$this->config->sessionVar])) session_id($_GET[$this->config->sessionVar]); + session_start(); + } + + /** + * Set the header info. + * + * @access public + * @return void + */ + public function sendHeader() + { + header("Content-Type: text/html; Language={$this->config->encoding}"); + header("Cache-control: private"); + } + + /** + * Set the commpany. + * + * First, search company by the http host. If not found, search by the default domain. Last, use the first as the default. + * After get the company, save it to session. + * @access public + * @return void + */ + public function setCompany() + { + $httpHost = $this->server->http_host; + if(strpos($httpHost, ":")) + { + $httpHost = explode(":", $httpHost); + $httpHost = $httpHost[0]; + } + + if($this->session->company and $this->session->company->pms == $httpHost) + { + $this->app->company = $this->session->company; + } + else + { + $company = $this->loadModel('company')->getByDomain(); + if(!$company and isset($this->config->default->domain)) $company = $this->company->getByDomain($this->config->default->domain); + if(!$company) $company = $this->company->getFirst(); + if(!$company) $this->app->error(sprintf($this->lang->error->companyNotFound, $httpHost), __FILE__, __LINE__, $exit = true); + $this->session->set('company', $company); + $this->app->company = $company; + } + } + + /** + * Set the user info. + * + * @access public + * @return void + */ + public function setUser() + { + if($this->session->user) + { + $this->app->user = $this->session->user; + } + elseif($this->app->company->guest) + { + $user = new stdClass(); + $user->id = 0; + $user->account = 'guest'; + $user->realname = 'guest'; + $user->rights = $this->loadModel('user')->authorize('guest'); + $this->session->set('user', $user); + $this->app->user = $this->session->user; + } + } + + /** + * Load configs from database and save it to config->system and config->personal. + * + * @access public + * @return void + */ + public function loadConfigFromDB() + { + if(!isset($this->app->user->account)) return; + + $account = $this->app->user->account; + $config = $this->loadModel('setting')->getSysAndPersonalConfig($account); + + $this->config->system = isset($config['system']) ? $config['system'] : array(); + $this->config->personal = isset($config[$account]) ? $config[$account] : array(); + + /* Overide the items defined in config/config.php and config/my.php. */ + if(isset($this->config->system->common)) + { + foreach($this->config->system->common as $record) + { + if($record->section) $this->config->{$record->section}->{$record->key} = $record->value; + if(!$record->section) $this->config->{$record->key} = $record->value; + } + } + } + + /** + * Juage a method of one module is open or not? + * + * @param string $module + * @param string $method + * @access public + * @return bool + */ + public function isOpenMethod($module, $method) + { + if($module == 'user' and strpos('login|logout|deny', $method) !== false) return true; + if($module == 'api' and $method == 'getsessionid') return true; + if($module == 'misc' and $method == 'about') return true; + if($module == 'misc' and $method == 'checkupdate') return true; + if($module == 'help' and $method == 'field') return true; + return false; + } + + /** + * Deny access. + * + * @access public + * @return void + */ + public function deny($module, $method) + { + $vars = "module=$module&method=$method"; + if(isset($this->server->http_referer)) + { + $referer = helper::safe64Encode($this->server->http_referer); + $vars .= "&referer=$referer"; + } + $denyLink = helper::createLink('user', 'deny', $vars); + + /* Fix the bug of IE: use js locate, can't get the referer. */ + if(strpos($this->server->http_user_agent, 'MSIE') !== false) + { + echo ""; + echo ""; + } + else + { + echo js::locate($denyLink); + } + exit; + } + + /** + * Get the run info. + * + * @param mixed $startTime the start time of this execution + * @access public + * @return array the run info array. + */ + public function getRunInfo($startTime) + { + $info['timeUsed'] = round(getTime() - $startTime, 4) * 1000; + $info['memory'] = round(memory_get_peak_usage() / 1024, 1); + $info['querys'] = count(dao::$querys); + return $info; + } + + /** + * Print top bar. + * + * @static + * @access public + * @return void + */ + public static function printTopBar() + { + global $lang, $app; + + printf($lang->todayIs, date(DT_DATE4)); + if(isset($app->user)) echo $app->user->realname . ' '; + if(isset($app->user) and $app->user->account != 'guest') + { + echo html::a(helper::createLink('user', 'logout'), $lang->logout); + } + else + { + echo html::a(helper::createLink('user', 'login'), $lang->login); + } + + echo ' |  '; + echo html::a(helper::createLink('misc', 'about'), $lang->aboutZenTao, '', "class='about'"); + echo $lang->agileTraining; + echo $lang->donate; + + echo ' | '; + echo html::select('', $app->config->langs, $app->cookie->lang, 'onchange="selectLang(this.value)"'); + echo html::select('', $app->lang->themes, $app->cookie->theme, 'onchange="selectTheme(this.value)"'); + } + + /** + * Print the main menu. + * + * @param string $moduleName + * @static + * @access public + * @return void + */ + public static function printMainmenu($moduleName) + { + global $app, $lang; + echo "
      \n"; + + /* Set the main main menu. */ + $mainMenu = $moduleName; + if(isset($lang->menugroup->$moduleName)) $mainMenu = $lang->menugroup->$moduleName; + + /* Sort menu according to menuOrder. */ + if(isset($lang->menuOrder)) + { + $menus = $lang->menu; + unset($lang->menu); + ksort($lang->menuOrder, SORT_ASC); + foreach($lang->menuOrder as $order) + { + $menu = $menus->$order; + unset($menus->$order); + $lang->menu->$order = $menu; + } + foreach($menus as $key => $menu) + { + $lang->menu->$key = $menu; + } + } + + + /* Print all main menus. */ + foreach($lang->menu as $menuKey => $menu) + { + $active = $menuKey == $mainMenu ? "class='active'" : ''; + list($menuLabel, $module, $method) = explode('|', $menu); + + if(common::hasPriv($module, $method)) + { + $link = helper::createLink($module, $method); + echo "
    • $menuLabel
    • \n"; + } + } + + } + + /** + * Print the search box. + * + * @static + * @access public + * @return void + */ + public static function printSearchBox() + { + global $app, $lang; + $moduleName = $app->getModuleName(); + $methodName = $app->getMethodName(); + $searchObject = $moduleName; + + if($moduleName == 'product') + { + if($methodName == 'browse') $searchObject = 'story'; + } + elseif($moduleName == 'project') + { + if(strpos('task|story|bug|build', $methodName) !== false) $searchObject = $methodName; + } + elseif($moduleName == 'my' or $moduleName == 'user') + { + $searchObject = $methodName; + } + + echo ""; + echo "
    \n"; + } + + /** + * Print the module menu. + * + * @param string $moduleName + * @static + * @access public + * @return void + */ + public static function printModuleMenu($moduleName) + { + global $lang, $app; + + if(!isset($lang->$moduleName->menu)) {echo "
      "; return;} + + /* Get the sub menus of the module, and get current module and method. */ + $submenus = $lang->$moduleName->menu; + $currentModule = $app->getModuleName(); + $currentMethod = $app->getMethodName(); + + /* Sort the subMenu according to menuOrder. */ + if(isset($lang->$moduleName->menuOrder)) + { + $menus = $submenus; + unset($submenus); + ksort($lang->$moduleName->menuOrder, SORT_ASC); + if(isset($menus->list)) + { + $submenus->list = $menus->list; + unset($menus->list); + } + foreach($lang->$moduleName->menuOrder as $order) + { + if(($order != 'list') && isset($menus->$order)) + { + $subOrder = $menus->$order; + unset($menus->$order); + $submenus->$order = $subOrder; + } + } + foreach($menus as $key => $menu) + { + $submenus->$key = $menu; + } + } + + /* The beginning of the menu. */ + echo "
        \n"; + + /* Cycling to print every sub menus. */ + foreach($submenus as $subMenuKey => $submenu) + { + /* Init the these vars. */ + $link = $submenu; + $subModule = ''; + $alias = ''; + $float = ''; + $active = ''; + $target = ''; + + if(is_array($submenu)) extract($submenu); // If the sub menu is an array, extract it. + + /* Print the menu. */ + if(strpos($link, '|') === false) + { + echo "
      • $link
      • \n"; + } + else + { + $link = explode('|', $link); + list($label, $module, $method) = $link; + $vars = isset($link[3]) ? $link[3] : ''; + if(common::hasPriv($module, $method)) + { + /* Is the currentModule active? */ + if($currentModule == $subModule and $float != 'right') $active = 'active'; + if($module == $currentModule and ($method == $currentMethod or strpos(",$alias,", ",$currentMethod,") !== false) and $float != 'right') $active = 'active'; + echo "
      • " . html::a(helper::createLink($module, $method, $vars), $label, $target, "id=submenu$subMenuKey") . "
      • \n"; + } + } + } + echo "
      \n"; + } + + /** + * Print the bread menu. + * + * @param string $moduleName + * @param string $position + * @static + * @access public + * @return void + */ + public static function printBreadMenu($moduleName, $position) + { + global $lang; + $mainMenu = $moduleName; + if(isset($lang->menugroup->$moduleName)) $mainMenu = $lang->menugroup->$moduleName; + echo html::a(helper::createLink('my', 'index'), $lang->ZenTaoPMS) . $lang->arrow; + if($moduleName != 'index') + { + list($menuLabel, $module, $method) = explode('|', $lang->menu->$mainMenu); + echo html::a(helper::createLink($module, $method), $menuLabel); + } + else + { + echo $lang->index->common; + } + if(empty($position)) return; + echo $lang->arrow; + foreach($position as $key => $link) + { + echo $link; + if(isset($position[$key + 1])) echo $lang->arrow; + } + } + + + /** + * Diff two string. (see phpt) + * + * @param string $text1 + * @param string $text2 + * @static + * @access public + * @return string + */ + public static function diff($text1, $text2) + { + $text1 = str_replace(' ', '', trim($text1)); + $text2 = str_replace(' ', '', trim($text2)); + $w = explode("\n", $text1); + $o = explode("\n", $text2); + $w1 = array_diff_assoc($w,$o); + $o1 = array_diff_assoc($o,$w); + $w2 = array(); + $o2 = array(); + foreach($w1 as $idx => $val) $w2[sprintf("%03d<",$idx)] = sprintf("%03d- ", $idx+1) . "" . trim($val) . ""; + foreach($o1 as $idx => $val) $o2[sprintf("%03d>",$idx)] = sprintf("%03d+ ", $idx+1) . "" . trim($val) . ""; + $diff = array_merge($w2, $o2); + ksort($diff); + return implode("\n", $diff); + } + + /** + * Judge Suhosin Setting whether the actual size of post data is large than the setting size. + * + * @param int $numberOfItems + * @param int $columns + * @access public + * @return void + */ + public function judgeSuhosinSetting($numberOfItems, $columns) + { + if(extension_loaded('suhosin')) + { + $maxPostVars = ini_get('suhosin.post.max_vars'); + $maxRequestVars = ini_get('suhosin.request.max_vars'); + if($numberOfItems * $columns > $maxPostVars or $numberOfItems * $columns > $maxRequestVars) return true; + } + + return false; + } + + /** + * Get the previous and next object. + * + * @param string $type story|task|bug|case + * @param string $objectIDs + * @param string $objectID + * @access public + * @return void + */ + public function getPreAndNextObject($type, $objectID) + { + $table = ''; + switch($type) + { + case 'story' : $table = TABLE_STORY; break; + case 'task' : $table = TABLE_TASK; break; + case 'bug' : $table = TABLE_BUG; break; + case 'testcase' : $table = TABLE_CASE; break; + default:break; + } + + $typeIDs = $type . 'IDs'; + if($this->session->$typeIDs) + { + $objectIDs = $this->session->$typeIDs; + $this->session->set($typeIDs, ''); + } + else + { + /* Get objectIDs. */ + $queryCondition = $type . 'QueryCondition'; + $queryCondition = $this->session->$queryCondition; + $orderBy = $type . 'OrderBy'; + $orderBy = $this->session->$orderBy; + $orderBy = str_replace('`left`', 'left', $orderBy); // process the `left` to left. + $objects = $this->dao->select('*')->from($table) + ->beginIF($queryCondition != false)->where($queryCondition)->fi() + ->beginIF($orderBy != false)->orderBy($orderBy)->fi() + ->fetchAll(); + $tmpObjectIDs = array(); + foreach($objects as $object) $tmpObjectIDs[$object->id] = $object->id; + $objectIDs = ',' . implode(',', $tmpObjectIDs) . ','; + $this->session->set($type . 'IDs', $objectIDs); + } + + /* Current object. */ + $currentStart = strpos($objectIDs, ',' . $objectID . ',') + 1; + $currentEnd = $currentStart + strlen($objectID) - 1; + + /* Get the previous object. */ + $tmp = substr($objectIDs, 0, $currentStart - 1); + $preStart = strrpos($tmp, ',', 0) + 1; + $preEnd = $currentStart - 2; + if($preEnd - $preStart < 0) + { + $preAndNextObject->pre = ''; + } + else + { + $preID = substr($objectIDs, $preStart, $preEnd - $preStart + 1); + $preAndNextObject->pre = $this->dao->select('*')->from($table)->where('id')->eq($preID)->fetch(); + } + + /* Get the next object. */ + $nextStart = $currentEnd + 2; + $nextEnd = strlen($objectIDs) > $nextStart ? strpos($objectIDs, ',', $nextStart) - 1 : 0; + if($nextEnd - $nextStart < 0) + { + $preAndNextObject->next = ''; + } + else + { + $nextID = substr($objectIDs, $nextStart, $nextEnd - $nextStart + 1); + $preAndNextObject->next = $this->dao->select('*')->from($table)->where('id')->eq($nextID)->fetch(); + } + + return $preAndNextObject; + } + + /** + * Save one executed query. + * + * @param string $sql + * @param string $objectType story|task|bug|testcase + * @access public + * @return void + */ + public function saveQueryCondition($sql, $objectType) + { + /* Set the query condition session. */ + $queryCondition = explode('WHERE', $sql); + $queryCondition = explode('ORDER', $queryCondition[1]); + $queryCondition = str_replace('t1.', '', $queryCondition[0]); + $this->session->set($objectType . 'QueryCondition', $queryCondition); + + /* Set the query condition session. */ + $orderBy = explode('ORDER BY', $sql); + if(isset($orderBy[1])) + { + $orderBy = explode('limit', $orderBy[1]); + $orderBy = str_replace('t1.', '', $orderBy[0]); + $this->session->set($objectType . 'OrderBy', $orderBy); + } + else + { + $this->session->set($objectType . 'OrderBy', ''); + } + } +} diff --git a/trunk/module/common/view/action.html.php b/trunk/module/common/view/action.html.php new file mode 100644 index 0000000000..8c62023968 --- /dev/null +++ b/trunk/module/common/view/action.html.php @@ -0,0 +1,68 @@ + +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + + + + +
      +
      + + history . $lang->reverse;?> + switchDisplay;?> + + + + +
      + history . $lang->reverse;?> + switchDisplay;?> +
      + + +
        + + +
      1. + actor])) $action->actor = $users[$action->actor]; + if($action->action == 'assigned' and isset($users[$action->extra]) ) $action->extra = $users[$action->extra]; + if(strpos($action->actor, ':') !== false) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1); + ?> + + action->printAction($action);?> + history)) echo html::commonButton($lang->unfold, "id=switchButton$i onclick=switchChange($i,this.value)");?> + + comment) or !empty($action->history)):?> + comment)) echo "
        ";?> + + comment) echo nl2br($action->comment);?> + comment)) echo "
        ";?> + +
      2. + +
      + + + + +
      + +
      diff --git a/trunk/module/common/view/alert.html.php b/trunk/module/common/view/alert.html.php new file mode 100644 index 0000000000..08a3e39503 --- /dev/null +++ b/trunk/module/common/view/alert.html.php @@ -0,0 +1,10 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +app->getWebRoot(); +$jsRoot = $webRoot . "js/"; +$defaultTheme = $webRoot . 'theme/default/'; +if($config->debug) +{ + css::import($defaultTheme . 'alert.css'); + js::import($jsRoot . 'jquery/alert/min.js'); +} diff --git a/trunk/module/common/view/autocomplete.html.php b/trunk/module/common/view/autocomplete.html.php new file mode 100644 index 0000000000..3479f8a01c --- /dev/null +++ b/trunk/module/common/view/autocomplete.html.php @@ -0,0 +1,5 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + diff --git a/trunk/module/common/view/chosen.html.php b/trunk/module/common/view/chosen.html.php new file mode 100755 index 0000000000..50dd848a7b --- /dev/null +++ b/trunk/module/common/view/chosen.html.php @@ -0,0 +1,19 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +debug) +{ + css::import($defaultTheme . 'chosen.css'); + js::import($jsRoot . 'jquery/chosen/chosen.min.js'); +} +?> + + diff --git a/trunk/module/common/view/colorbox.html.php b/trunk/module/common/view/colorbox.html.php new file mode 100644 index 0000000000..5fec18d7a6 --- /dev/null +++ b/trunk/module/common/view/colorbox.html.php @@ -0,0 +1,7 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +debug) +{ + css::import($defaultTheme . 'colorbox.css'); + js::import($jsRoot . 'jquery/colorbox/min.js'); +} diff --git a/trunk/module/common/view/colorize.html.php b/trunk/module/common/view/colorize.html.php new file mode 100644 index 0000000000..4c49747147 --- /dev/null +++ b/trunk/module/common/view/colorize.html.php @@ -0,0 +1,10 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +debug) js::import($jsRoot . 'jquery/colorize/min.js');?> + diff --git a/trunk/module/common/view/datepicker.html.php b/trunk/module/common/view/datepicker.html.php new file mode 100644 index 0000000000..455c73c0bb --- /dev/null +++ b/trunk/module/common/view/datepicker.html.php @@ -0,0 +1,41 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +debug) +{ + css::import($defaultTheme . 'datepicker.css'); + js::import($jsRoot . 'jquery/datepicker/min.js'); + js::import($jsRoot . 'jquery/datepicker/date.js'); +} +?> + diff --git a/trunk/module/common/view/footer.html.php b/trunk/module/common/view/footer.html.php new file mode 100644 index 0000000000..d6e8fa4651 --- /dev/null +++ b/trunk/module/common/view/footer.html.php @@ -0,0 +1,35 @@ + + getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + +
      + + + + + + + + diff --git a/trunk/module/common/view/footer.lite.html.php b/trunk/module/common/view/footer.lite.html.php new file mode 100644 index 0000000000..8008f62818 --- /dev/null +++ b/trunk/module/common/view/footer.lite.html.php @@ -0,0 +1,9 @@ + +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + + + + diff --git a/trunk/module/common/view/header.html.php b/trunk/module/common/view/header.html.php new file mode 100644 index 0000000000..9161347c42 --- /dev/null +++ b/trunk/module/common/view/header.html.php @@ -0,0 +1,29 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();} +include 'header.lite.html.php'; +include 'colorbox.html.php'; +include 'chosen.html.php'; +//include 'validation.html.php'; +?> + + + +
      + +
      diff --git a/trunk/module/common/view/header.lite.html.php b/trunk/module/common/view/header.lite.html.php new file mode 100644 index 0000000000..b5cfa534c7 --- /dev/null +++ b/trunk/module/common/view/header.lite.html.php @@ -0,0 +1,41 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +app->getWebRoot(); +$jsRoot = $webRoot . "js/"; +$themeRoot = $webRoot . "theme/"; +$defaultTheme = $webRoot . 'theme/default/'; +$langTheme = $themeRoot . 'lang/' . $app->getClientLang() . '.css'; +$clientTheme = $this->app->getClientTheme(); +?> + + + + + title)) $header->title = $lang->ZenTaoPMS; + echo html::title($header->title . ' - ' . $lang->ZenTaoPMS); + + js::exportConfigVars(); + if($config->debug) + { + js::import($jsRoot . 'jquery/lib.js', $config->version); + js::import($jsRoot . 'my.min.js', $config->version); + + css::import($defaultTheme . 'yui.css', $config->version); + css::import($defaultTheme . 'style.css', $config->version); + css::import($langTheme, $config->version); + if(strpos($clientTheme, 'default') === false) css::import($clientTheme . 'style.css', $config->version); + } + else + { + js::import($jsRoot . 'all.js', $config->version); + css::import($defaultTheme . $this->cookie->lang . '.' . $this->cookie->theme . '.css', $config->version); + } + + if(isset($pageCss)) css::internal($pageCss); + + echo html::icon($webRoot . 'favicon.ico'); + ?> + + diff --git a/trunk/module/common/view/jquerytools.html.php b/trunk/module/common/view/jquerytools.html.php new file mode 100644 index 0000000000..7c878af3b0 --- /dev/null +++ b/trunk/module/common/view/jquerytools.html.php @@ -0,0 +1,2 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + diff --git a/trunk/module/common/view/kindeditor.html.php b/trunk/module/common/view/kindeditor.html.php new file mode 100644 index 0000000000..1ff1c058da --- /dev/null +++ b/trunk/module/common/view/kindeditor.html.php @@ -0,0 +1,50 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +moduleName; +$method = $this->methodName; +if(!isset($config->$module->editor->$method)) return; +$editor = $config->$module->editor->$method; +$editor['id'] = explode(',', $editor['id']); +?> + + diff --git a/trunk/module/common/view/mail.html.php b/trunk/module/common/view/mail.html.php new file mode 100644 index 0000000000..854cdb1fc0 --- /dev/null +++ b/trunk/module/common/view/mail.html.php @@ -0,0 +1,17 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +actor])) $action->actor = $users[$action->actor];?> +action->printAction($action);?> +comment) or !empty($action->history)):?> +
      +
      action->printChanges($action->objectType, $action->history);?>
      +comment and $action->history) echo '
      '; echo nl2br($action->comment);?> +
      + + diff --git a/trunk/module/common/view/sparkline.html.php b/trunk/module/common/view/sparkline.html.php new file mode 100644 index 0000000000..7696e8a20e --- /dev/null +++ b/trunk/module/common/view/sparkline.html.php @@ -0,0 +1,2 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + diff --git a/trunk/module/common/view/tablesorter.html.php b/trunk/module/common/view/tablesorter.html.php new file mode 100644 index 0000000000..2f68b1e1b6 --- /dev/null +++ b/trunk/module/common/view/tablesorter.html.php @@ -0,0 +1,36 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + + diff --git a/trunk/module/common/view/treetable.html.php b/trunk/module/common/view/treetable.html.php new file mode 100644 index 0000000000..f72e6fbae0 --- /dev/null +++ b/trunk/module/common/view/treetable.html.php @@ -0,0 +1,6 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + + diff --git a/trunk/module/common/view/treeview.html.php b/trunk/module/common/view/treeview.html.php new file mode 100644 index 0000000000..d56904e12c --- /dev/null +++ b/trunk/module/common/view/treeview.html.php @@ -0,0 +1,9 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> +debug) +{ + css::import($defaultTheme . 'treeview.css'); + js::import($jsRoot . 'jquery/treeview/min.js'); +} +?> + diff --git a/trunk/module/common/view/validation.html.php b/trunk/module/common/view/validation.html.php new file mode 100644 index 0000000000..ae0f6aee90 --- /dev/null +++ b/trunk/module/common/view/validation.html.php @@ -0,0 +1,103 @@ +getExtViewFile(__FILE__)){include $extView; return helper::cd();}?> + + + diff --git a/trunk/module/company/config.php b/trunk/module/company/config.php new file mode 100644 index 0000000000..a33225b140 --- /dev/null +++ b/trunk/module/company/config.php @@ -0,0 +1,22 @@ +company->create->requiredFields = 'name,pms'; +$config->company->edit->requiredFields = 'name,pms'; + +global $lang, $app; +$app->loadLang('action'); +$config->company->dynamic->search['module'] = 'action'; +$config->company->dynamic->search['fields']['product'] = $lang->action->product; +$config->company->dynamic->search['fields']['actor'] = $lang->action->actor; +$config->company->dynamic->search['fields']['objectID'] = $lang->action->objectID; +$config->company->dynamic->search['fields']['project'] = $lang->action->project; +$config->company->dynamic->search['fields']['objectType'] = $lang->action->objectType; +$config->company->dynamic->search['fields']['date'] = $lang->action->date; +$config->company->dynamic->search['fields']['action'] = $lang->action->action; + +$config->company->dynamic->search['params']['product'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->company->dynamic->search['params']['actor'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->company->dynamic->search['params']['objectID'] = array('operator' => '=', 'control' => 'input', 'values' => ''); +$config->company->dynamic->search['params']['project'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->company->dynamic->search['params']['objectType'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->action->search->objectTypeList); +$config->company->dynamic->search['params']['date'] = array('operator' => '>=', 'control' => 'input', 'values' => ''); +$config->company->dynamic->search['params']['action'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->action->search->label); diff --git a/trunk/module/company/control.php b/trunk/module/company/control.php new file mode 100644 index 0000000000..e6f04ff21e --- /dev/null +++ b/trunk/module/company/control.php @@ -0,0 +1,237 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +class company extends control +{ + /** + * Construct function, load dept and user models auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('dept'); + $this->app->loadLang('user'); + $this->company->setMenu(); + } + + /** + * Index page, header to browse. + * + * @access public + * @return void + */ + public function index() + { + $this->locate($this->createLink('company', 'browse')); + } + + /** + * Browse departments and users of a company. + * + * @param int $deptID + * @access public + * @return void + */ + public function browse($deptID = 0) + { + $this->lang->set('menugroup.company', 'company'); + $childDeptIds = $this->dept->getAllChildID($deptID); + + $this->company->setMenu($deptID); + + $header['title'] = $this->lang->company->index . $this->lang->colon . $this->lang->dept->common; + $position[] = $this->lang->dept->common; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->users = $this->dept->getUsers($childDeptIds); + $this->view->deptTree = $this->dept->getTreeMenu($rooteDeptID = 0, array('deptModel', 'createMemberLink')); + $this->view->parentDepts = $this->dept->getParents($deptID); + $this->view->deptID = $deptID; + + $this->display(); + } + + /** + * Create a company. + * + * @access public + * @return void + */ + public function create() + { + if(!empty($_POST)) + { + $this->company->create(); + if(dao::isError()) die(js::error(dao::getError())); + die(js::locate($this->createLink('admin', 'browsecompany'), 'parent')); + } + + $this->lang->set('menugroup.company', 'admin'); + $this->lang->company->menu = $this->lang->admin->menu; + $this->lang->company->menuOrder = $this->lang->admin->menuOrder; + + $header['title'] = $this->lang->admin->common . $this->lang->colon . $this->lang->company->create; + $position[] = html::a($this->createLink('admin', 'browsecompany'), $this->lang->admin->company); + $position[] = $this->lang->company->create; + $this->view->header = $header; + $this->view->position = $position; + + $this->display(); + } + + /** + * Edit a company. + * + * @access public + * @return void + */ + public function edit() + { + if(!empty($_POST)) + { + $this->company->update(); + if(dao::isError()) die(js::error(dao::getError())); + die(js::alert($this->lang->company->successSaved)); + } + + $header['title'] = $this->lang->company->common . $this->lang->colon . $this->lang->company->edit; + $position[] = $this->lang->company->edit; + $this->view->header = $header; + $this->view->position = $position; + $this->view->company = $this->company->getById($this->app->company->id); + + $this->display(); + } + + /** + * Delete a company. + * + * @param int $companyID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($companyID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm($this->lang->company->confirmDelete, $this->createLink('company', 'delete', "companyID=$companyID&confirm=yes")); + exit; + } + else + { + $this->company->delete($companyID); + echo js::locate($this->createLink('admin', 'browseCompany'), 'parent'); + exit; + } + } + + /** + * Company dynamic. + * + * @param string $browseType + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function dynamic($browseType = 'today', $param = '', $orderBy = 'date_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $this->app->loadLang('user'); + $this->app->loadLang('project'); + $this->loadModel('action'); + + /* Save session. */ + $uri = $this->app->getURI(true); + $this->session->set('productList', $uri); + $this->session->set('productPlanList', $uri); + $this->session->set('releaseList', $uri); + $this->session->set('storyList', $uri); + $this->session->set('projectList', $uri); + $this->session->set('taskList', $uri); + $this->session->set('buildList', $uri); + $this->session->set('bugList', $uri); + $this->session->set('caseList', $uri); + $this->session->set('testtaskList', $uri); + + /* Set the pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; + + /* Set the user and type. */ + $account = $browseType == 'account' ? $param : 'all'; + $product = $browseType == 'product' ? $param : 'all'; + $project = $browseType == 'project' ? $param : 'all'; + $period = ($browseType == 'account' or $browseType == 'product' or $browseType == 'project') ? 'all' : $browseType; + $queryID = ($browseType == 'bysearch') ? (int)$param : 0; + + /* Get products' list.*/ + $products = $this->loadModel('product')->getPairs(); + $products = array($this->lang->product->select) + $products; + $this->view->products = $products; + + /* Get projects' list.*/ + $projects = $this->loadModel('project')->getPairs(); + $projects = array($this->lang->project->select) + $projects; + $this->view->projects = $projects; + + /* Get users.*/ + $users = $this->loadModel('user')->getPairs('nodeleted|noclosed'); + $users[''] = $this->lang->user->select; + $this->view->users = $users; + + /* The header and position. */ + $this->view->header->title = $this->lang->company->common . $this->lang->colon . $this->lang->company->dynamic; + $this->view->position[] = $this->lang->company->dynamic; + + /* Get actions. */ + if($browseType != 'bysearch') + { + $actions = $this->action->getDynamic($account, $period, $orderBy, $pager, $product, $project); + } + else + { + $actions = $this->action->getDynamicBySearch($products, $projects, $queryID, $orderBy, $pager); + } + + /* Build search form. */ + $projects[0] = ''; + $products[0] = ''; + $users[''] = ''; + ksort($projects); + ksort($products); + $projects['all'] = $this->lang->project->allProject; + $products['all'] = $this->lang->product->allProduct; + $this->config->company->dynamic->search['actionURL'] = $this->createLink('company', 'dynamic', "browseType=bysearch¶m=myQueryID"); + $this->config->company->dynamic->search['queryID'] = $queryID; + $this->config->company->dynamic->search['params']['project']['values'] = $projects; + $this->config->company->dynamic->search['params']['product']['values'] = $products; + $this->config->company->dynamic->search['params']['actor']['values'] = $users; + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->company->dynamic->search); + + /* Assign. */ + $this->view->browseType = $browseType; + $this->view->account = $account; + $this->view->product = $product; + $this->view->project = $project; + $this->view->queryID = $queryID; + $this->view->actions = $actions; + $this->display(); + } +} diff --git a/trunk/module/company/js/dynamic.js b/trunk/module/company/js/dynamic.js new file mode 100644 index 0000000000..2b4de64f54 --- /dev/null +++ b/trunk/module/company/js/dynamic.js @@ -0,0 +1,15 @@ +function changeUser(account) +{ + link = createLink('company', 'dynamic', 'type=account¶m=' + account); + location.href = link; +} +function changeProject(project) +{ + link = createLink('company', 'dynamic', 'type=project¶m=' + project); + location.href = link; +} +function changeProduct(product) +{ + link = createLink('company', 'dynamic', 'type=product¶m=' + product); + location.href = link; +} diff --git a/trunk/module/company/js/edit.js b/trunk/module/company/js/edit.js new file mode 100644 index 0000000000..43061492f9 --- /dev/null +++ b/trunk/module/company/js/edit.js @@ -0,0 +1 @@ +function setForm(){} diff --git a/trunk/module/company/lang/en.php b/trunk/module/company/lang/en.php new file mode 100644 index 0000000000..667deabd55 --- /dev/null +++ b/trunk/module/company/lang/en.php @@ -0,0 +1,39 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->company->common = 'Company'; +$lang->company->index = "Index"; +$lang->company->create = "Create"; +$lang->company->edit = "Edit"; +$lang->company->read = "Info"; +$lang->company->update = "Update"; +$lang->company->delete = "Delete"; +$lang->company->browse = "User"; +$lang->company->dynamic = "Dynamic"; +$lang->company->depts = "Dept"; +$lang->company->orgView = 'Company'; + +$lang->company->confirmDelete = "Are you sure to delete this company?"; +$lang->company->successSaved = "Success saved"; + +$lang->company->id = 'ID'; +$lang->company->name = 'Name'; +$lang->company->phone = 'Phone'; +$lang->company->fax = 'Fax'; +$lang->company->address = 'Address'; +$lang->company->zipcode = 'Zipcode'; +$lang->company->website = 'Web site'; +$lang->company->backyard = 'Internal Site'; +$lang->company->pms = 'ZenTaoPMS Site'; +$lang->company->guest = 'Guest visit'; + +$lang->company->guestList[0] = 'Deny'; +$lang->company->guestList[1] = 'Allow'; diff --git a/trunk/module/company/lang/zh-cn.php b/trunk/module/company/lang/zh-cn.php new file mode 100644 index 0000000000..40bc8e8001 --- /dev/null +++ b/trunk/module/company/lang/zh-cn.php @@ -0,0 +1,39 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->company->common = '组织视图'; +$lang->company->index = "组织视图首页"; +$lang->company->create = "新增公司"; +$lang->company->edit = "编辑公司"; +$lang->company->read = "公司信息"; +$lang->company->update = "更新公司"; +$lang->company->delete = "删除公司"; +$lang->company->browse = "用户列表"; +$lang->company->dynamic = "组织动态"; +$lang->company->depts = "部门列表"; +$lang->company->orgView = '组织视图'; + +$lang->company->confirmDelete = "您确定删除该公司吗?"; +$lang->company->successSaved = "成功保存"; + +$lang->company->id = '编号'; +$lang->company->name = '公司名称'; +$lang->company->phone = '联系电话'; +$lang->company->fax = '传真'; +$lang->company->address = '通讯地址'; +$lang->company->zipcode = '邮政编码'; +$lang->company->website = '公司官网'; +$lang->company->backyard = '公司内网'; +$lang->company->pms = 'PMS网站'; +$lang->company->guest = '匿名登录'; + +$lang->company->guestList[0] = '不允许'; +$lang->company->guestList[1] = '允许'; diff --git a/trunk/module/company/lang/zh-tw.php b/trunk/module/company/lang/zh-tw.php new file mode 100644 index 0000000000..74b08a119c --- /dev/null +++ b/trunk/module/company/lang/zh-tw.php @@ -0,0 +1,39 @@ + + * @package company + * @version $Id: zh-tw.php 3173 2012-07-01 00:05:16Z wwccss $ + * @link http://www.zentao.net + */ +$lang->company->common = '組織視圖'; +$lang->company->index = "組織視圖首頁"; +$lang->company->create = "新增公司"; +$lang->company->edit = "編輯公司"; +$lang->company->read = "公司信息"; +$lang->company->update = "更新公司"; +$lang->company->delete = "刪除公司"; +$lang->company->browse = "用戶列表"; +$lang->company->dynamic = "組織動態"; +$lang->company->depts = "部門列表"; +$lang->company->orgView = '組織視圖'; + +$lang->company->confirmDelete = "您確定刪除該公司嗎?"; +$lang->company->successSaved = "成功保存"; + +$lang->company->id = '編號'; +$lang->company->name = '公司名稱'; +$lang->company->phone = '聯繫電話'; +$lang->company->fax = '傳真'; +$lang->company->address = '通訊地址'; +$lang->company->zipcode = '郵政編碼'; +$lang->company->website = '公司官網'; +$lang->company->backyard = '公司內網'; +$lang->company->pms = 'PMS網站'; +$lang->company->guest = '匿名登錄'; + +$lang->company->guestList[0] = '不允許'; +$lang->company->guestList[1] = '允許'; diff --git a/trunk/module/company/model.php b/trunk/module/company/model.php new file mode 100644 index 0000000000..c7a0df4d20 --- /dev/null +++ b/trunk/module/company/model.php @@ -0,0 +1,123 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +?> +lang->company->menu, 'name', array($this->app->company->name)); + common::setMenuVars($this->lang->company->menu, 'addUser', array($dept)); + } + + /** + * Get company list. + * + * @access public + * @return void + */ + public function getList() + { + return $this->dao->select('*')->from(TABLE_COMPANY)->fetchAll(); + } + + /** + * Get the first company. + * + * @access public + * @return void + */ + public function getFirst() + { + return $this->dao->select('*')->from(TABLE_COMPANY)->orderBy('id')->limit(1)->fetch(); + } + + /** + * get company by domain. + * + * @param string $domain if empty, use current HTTP_HOST. + * @access public + * @return object + */ + public function getByDomain($domain = '') + { + if(empty($domain)) $domain = $this->server->http_host; + return $this->dao->findByPMS($domain)->from(TABLE_COMPANY)->fetch(); + } + + /** + * Get company info by id. + * + * @param int $companyID + * @access public + * @return object + */ + public function getByID($companyID = '') + { + return $this->dao->findById((int)$companyID)->from(TABLE_COMPANY)->fetch(); + } + + /** + * Create a company. + * + * @access public + * @return void + */ + public function create() + { + $company = fixer::input('post')->get(); + $this->dao->insert(TABLE_COMPANY) + ->data($company) + ->autoCheck() + ->batchCheck($this->config->company->create->requiredFields, 'notempty') + ->batchCheck('name,pms', 'unique') + ->exec(); + } + + /** + * Update a company. + * + * @access public + * @return void + */ + public function update() + { + $company = fixer::input('post')->stripTags('name')->get(); + $companyID = $this->app->company->id; + $this->dao->update(TABLE_COMPANY) + ->data($company) + ->autoCheck() + ->batchCheck($this->config->company->edit->requiredFields, 'notempty') + ->batchCheck('name,pms', 'unique', "id != '$companyID'") + ->where('id')->eq($companyID) + ->exec(); + } + + /** + * Delete a company. + * + * @param int $companyID + * @access public + * @return void + */ + public function delete($companyID) + { + return $this->dao->delete()->from(TABLE_COMPANY)->where('id')->eq((int)$companyID)->limit(1)->exec(); + } +} diff --git a/trunk/module/company/view/browse.html.php b/trunk/module/company/view/browse.html.php new file mode 100644 index 0000000000..746eee8f7e --- /dev/null +++ b/trunk/module/company/view/browse.html.php @@ -0,0 +1,78 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + +
      +
      dept->common;?>
      +
      + +
      + user->create);echo '
      '; + common::printLink('company', 'browse', '', $lang->user->allUsers); echo '
      '; + common::printLink('dept', 'browse', '', $lang->dept->manage); + ?> +
      +
      +
      + + + + + + + user->nickname;?> + + + + + + + + + + + + + + + + nickname;?> + + + + + + + + + + +
      idAB;?>user->realname;?>user->account;?>user->email;?>user->gender;?>user->phone;?>user->join;?>user->last;?>user->visits;?>actions;?>
      id;?>account", $user->realname)) echo $user->realname;?>account;?>email);?>user->genderList->{$user->gender})) echo $lang->user->genderList->{$user->gender};?>phone;?>join;?>last);?>visits;?> + id&from=company", $lang->edit); + common::printLink('user', 'delete', "userID=$user->id", $lang->delete, "hiddenwin"); + ?> +
      +
      + + diff --git a/trunk/module/company/view/create.html.php b/trunk/module/company/view/create.html.php new file mode 100644 index 0000000000..dd5fa42dd1 --- /dev/null +++ b/trunk/module/company/view/create.html.php @@ -0,0 +1,56 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      company->create;?>
      company->name;?>
      company->phone;?>
      company->fax;?>
      company->address;?>
      company->zipcode;?>
      company->website;?>
      company->backyard;?>
      company->pms;?>
      company->guest;?>company->guestList);?>
      +
      + diff --git a/trunk/module/company/view/dynamic.html.php b/trunk/module/company/view/dynamic.html.php new file mode 100644 index 0000000000..4dbc2c9c0d --- /dev/null +++ b/trunk/module/company/view/dynamic.html.php @@ -0,0 +1,62 @@ +dynamic view file of dashboard module of ZenTaoPMS. + * + * @copyright Copyright 2009-2012 青岛易软天创网络科技有限公司 (QingDao Nature Easy Soft Network Technology Co,LTD www.cnezsoft.com) + * @license LGPL (http://www.gnu.org/licenses/lgpl.html) + * @author Chunsheng Wang + * @package dashboard + * @version $Id: action->dynamic.html.php 1477 2011-03-01 15:25:50Z wwccss $ + * @link http://www.zentao.net + */ +?> + + + +
      + ' . html::a(inlink('dynamic', "browseType=today"), $lang->action->dynamic->today) . ''; + echo '' . html::a(inlink('dynamic', "browseType=yesterday"), $lang->action->dynamic->yesterday) . ''; + echo '' . html::a(inlink('dynamic', "browseType=twodaysago"), $lang->action->dynamic->twoDaysAgo) . ''; + echo '' . html::a(inlink('dynamic', "browseType=thisweek"), $lang->action->dynamic->thisWeek) . ''; + echo '' . html::a(inlink('dynamic', "browseType=lastweek"), $lang->action->dynamic->lastWeek) . ''; + echo '' . html::a(inlink('dynamic', "browseType=thismonth"), $lang->action->dynamic->thisMonth) . ''; + echo '' . html::a(inlink('dynamic', "browseType=lastmonth"), $lang->action->dynamic->lastMonth) . ''; + echo '' . html::a(inlink('dynamic', "browseType=all"), $lang->action->dynamic->all) . ''; + echo "" . html::select('account', $users, $account, 'onchange=changeUser(this.value)') . ''; + echo "" . html::select('product', $products, $product, 'onchange=changeProduct(this.value)') . ''; + echo "" . html::select('project', $projects, $project, 'onchange=changeProject(this.value)') . ''; + echo "" . html::a('#', '' . $lang->action->dynamic->search) . ""; + ?> +
      +
      '>
      + + + + + + + + + + + + + + objectType == 'case' ? 'testcase' : $action->objectType;?> + + + + + + + + + + + +
      action->date;?> action->actor;?>action->action;?> action->objectType;?> idAB;?>action->objectName;?>
      date;?>actor]) ? print($users[$action->actor]) : print($action->actor);?>actionLabel;?>action->objectTypes[$action->objectType];?>objectID;?>objectLink, $action->objectName);?>
      show();?>
      + + diff --git a/trunk/module/company/view/edit.html.php b/trunk/module/company/view/edit.html.php new file mode 100644 index 0000000000..d55865af17 --- /dev/null +++ b/trunk/module/company/view/edit.html.php @@ -0,0 +1,56 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      company->edit;?>
      company->name;?>name, "class='text-1'");?>
      company->phone;?>phone, "class='text-1'");?>
      company->fax;?>fax, "class='text-1'");?>
      company->address;?>address, "class='text-1'");?>
      company->zipcode;?>zipcode, "class='text-1'");?>
      company->website;?>website, "class='text-1'");?>
      company->backyard;?>backyard, "class='text-1'");?>
      company->pms;?>pms, "class='text-1'");?>
      company->guest;?>company->guestList, $company->guest);?>
      +
      + diff --git a/trunk/module/convert/control.php b/trunk/module/convert/control.php new file mode 100644 index 0000000000..f2194a82f3 --- /dev/null +++ b/trunk/module/convert/control.php @@ -0,0 +1,240 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +class convert extends control +{ + /** + * Index page of convert. + * + * @access public + * @return void + */ + public function index() + { + $this->convert->saveState(); + $this->view->header->title = $this->lang->convert->common; + $this->display(); + } + + /** + * Select the source system. + * + * @access public + * @return void + */ + public function selectSource() + { + $this->view->header->title = $this->lang->convert->common . $this->lang->colon; + $this->display(); + } + + /** + * Set configs of converter. + * + * This is the extrance of every system. It will call the set function of corresponding module. + * + * @access public + * @return void + */ + public function setConfig() + { + if(!$this->post->source) + { + echo js::alert($this->lang->convert->mustSelectSource); + die(js::locate('back')); + } + list($sourceName, $version) = explode('_', $this->post->source); + $setFunc = "set$sourceName"; + $this->view->header->title = $this->lang->convert->setting; + $this->view->source = $sourceName; + $this->view->version = $version; + $this->view->setting = $this->fetch('convert', $setFunc, "version=$version"); + $this->display(); + } + + /** + * The setting page of bugfree. + * + * @param string $version + * @access public + * @return void + */ + public function setBugFree($version) + { + $this->view->source = 'BugFree'; + $this->view->version = $version; + $this->view->tablePrefix = $version > 1 ? 'bf_' : ''; + $this->view->dbName = $version > 1 ? 'bugfree2' : 'BugFree'; + $this->view->dbCharset = 'utf8'; + $this->display(); + } + + /** + * The setting page of Redmine. + * + * @param string $version + * @access public + * @return void + */ + public function setRedmine($version) + { + $this->view->source = 'Redmine'; + $this->view->version = $version; + $this->view->dbName = 'redmine'; + $this->view->dbCharset = 'utf8'; + $this->display(); + } + + /** + * Check config. Same as setConfig. + * + * @access public + * @return void + */ + public function checkConfig() + { + $checkFunc = 'check' . $this->post->source; + $this->view->header->title = $this->lang->convert->checkConfig; + $this->view->source = $this->post->source; + $this->view->checkResult = $this->fetch('convert', $checkFunc, "version={$this->post->version}"); + $this->display(); + } + + /** + * Check settings of bugfree. + * + * @param int $version + * @access public + * @return void + */ + public function checkBugFree($version) + { + helper::import('./converter/bugfree.php'); + $converter = new bugfreeConvertModel(); + + /* Check it. */ + $checkInfo['db'] = $converter->connectDB(); + //if(is_object($checkInfo['db'])) $checkInfo['table'] = $converter->checkTables(); + $checkInfo['path'] = $converter->checkPath(); + + /* Compute the checking result. */ + $result = 'pass'; + if(!is_object($checkInfo['db']) or !$checkInfo['path']) $result = 'fail'; + + /* Assign. */ + $this->view->version = $version; + $this->view->source = 'bugfree'; + $this->view->result = $result; + $this->view->checkInfo = $checkInfo; + $this->display(); + } + + /** + * Check settings of Redmine. + * + * @param int $version + * @access public + * @return void + */ + public function checkRedmine($version) + { + helper::import('./converter/redmine.php'); + $converter = new redmineConvertModel(); + + /* Check it. */ + $checkInfo['db'] = $converter->connectDB(); + $checkInfo['path'] = $converter->checkPath(); + + $this->view->trackers = $this->dao->dbh($converter->sourceDBH)->select('id, name')->from('trackers')->fetchAll('id', $autoCompany = false); + $this->view->statuses = $this->dao->dbh($converter->sourceDBH)->select('id, name')->from('issue_statuses')->fetchAll('id', $autoCompany = false); + $this->view->pries = $this->dao->dbh($converter->sourceDBH)->select('id, name')->from('enumerations')->where('type')->eq('IssuePriority')->fetchAll('id', $autoCompany = false); + /* Compute the checking result. */ + $result = 'pass'; + if(!is_object($checkInfo['db']) or !$checkInfo['path']) $result = 'fail'; + + $this->app->loadLang('bug'); + $this->app->loadLang('story'); + $this->app->loadLang('task'); + $this->view->aimTypeList['bug'] = 'bug'; + $this->view->aimTypeList['task'] = 'task'; + $this->view->aimTypeList['story'] = 'story'; + + /* Assign. */ + $this->view->version = $version; + $this->view->source = 'Redmine'; + $this->view->result = $result; + $this->view->checkInfo = $checkInfo; + $this->display(); + } + + /** + * Execute the converting. + * + * @access public + * @return void + */ + public function execute() + { + $convertFunc = 'convert' . $this->post->source; + $this->view->header->title = $this->lang->convert->execute; + $this->view->source = $this->post->source; + $this->view->version = $this->post->version; + + $this->view->executeResult = $this->fetch('convert', $convertFunc, "version={$this->post->version}"); + $this->display(); + } + + /** + * Convert bugfree. + * + * @param int $version + * @access public + * @return void + */ + public function convertBugFree($version) + { + helper::import('./converter/bugfree.php'); + helper::import("./converter/bugfree$version.php"); + $className = "bugfree{$version}ConvertModel"; + $converter = new $className(); + $this->view->version = $version; + $this->view->result = $converter->execute($version); + $this->view->info = bugfreeConvertModel::$info; + $this->display(); + } + + /** + * convert redmine + * + * @param int $version + * @access public + * @return void + */ + public function convertRedmine($version) + { + helper::import('./converter/redmine.php'); + helper::import("./converter/redmine$version.php"); + $className = "redmine11ConvertModel"; + $redmine->aimTypes = $this->post->aimTypes; + $redmine->statusTypes['bug'] = $this->post->statusTypesOfBug; + $redmine->statusTypes['story'] = $this->post->statusTypesOfStory; + $redmine->statusTypes['task'] = $this->post->statusTypesOfTask; + $redmine->priTypes['bug'] = $this->post->priTypesOfBug; + $redmine->priTypes['story'] = $this->post->priTypesOfStory; + $redmine->priTypes['task'] = $this->post->priTypesOfTask; + + $converter = new $className($redmine); + $this->view->version = $version; + $this->view->result = $converter->execute($version); + $this->view->info = redmineConvertModel::$info; + $this->display(); + } +} diff --git a/trunk/module/convert/converter/bugfree.php b/trunk/module/convert/converter/bugfree.php new file mode 100644 index 0000000000..e829fc4e80 --- /dev/null +++ b/trunk/module/convert/converter/bugfree.php @@ -0,0 +1,88 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +class bugfreeConvertModel extends convertModel +{ + public $map = array(); + public $filePath = ''; + static public $info = array(); + + /** + * Connect to db auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + parent::connectDB(); + } + + /** + * Check table. + * + * @access public + * @return bool + */ + public function checkTables() + { + return true; + } + + /** + * Check the install path. + * + * @access public + * @return bool + */ + public function checkPath() + { + $this->setPath(); + return file_exists($this->filePath); + } + + /** + * Set the path of attachments. + * + * @access public + * @return bool + */ + public function setPath() + { + $this->filePath = realpath($this->post->installPath) . $this->app->getPathFix() . 'BugFile' . $this->app->getPathFix(); + } + + /** + * Excute the convert. + * + * @param int $version + * @access public + * @return void + */ + public function execute($version) + { + } + + /** + * Clear rows added in converting. + * + * @access public + * @return void + */ + public function clear() + { + foreach($this->session->state as $table => $maxID) + { + $this->dao->dbh($this->dbh)->delete()->from($table)->where('id')->gt($maxID)->exec(); + } + } +} diff --git a/trunk/module/convert/converter/bugfree1.php b/trunk/module/convert/converter/bugfree1.php new file mode 100644 index 0000000000..2e60b09fab --- /dev/null +++ b/trunk/module/convert/converter/bugfree1.php @@ -0,0 +1,318 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +class bugfree1ConvertModel extends bugfreeConvertModel +{ + /** + * Execute the convert. + * + * @access public + * @return array + */ + public function execute() + { + $this->clear(); + $this->convertGroup(); + $result['users'] = $this->convertUser(); + $result['projects'] = $this->convertProject(); + $result['modules'] = $this->convertModule(); + $result['bugs'] = $this->convertBug(); + $result['actions'] = $this->convertAction(); + $result['files'] = $this->convertFile(); + $this->dao->dbh($this->dbh); + $this->loadModel('tree')->fixModulePath(); + return $result; + } + + /** + * Convert groups. + * + * @access public + * @return void + */ + public function convertGroup() + { + $groups = $this->dao->dbh($this->sourceDBH) + ->select("groupID AS id, groupName AS name, groupUser AS users") + ->from('BugGroup') + ->fetchAll('id', $autoCompany = false); + foreach($groups as $groupID => $group) + { + /* Explode into array. */ + $groupUsers = explode(',', $group->users); + unset($group->id); + unset($group->users); + + /* Insert the group. */ + $this->dao->dbh($this->dbh)->insert(TABLE_GROUP)->data($group)->exec(); + $zentaoGroupID = $this->dao->lastInsertId(); + + /* Insert account. */ + foreach($groupUsers as $account) + { + if(empty($account)) continue; + $this->dao->dbh($this->dbh)->insert(TABLE_USERGROUP)->set('`group`')->eq($zentaoGroupID)->set('account')->eq($account)->exec(); + } + } + } + + /** + * Convert user. + * + * @access public + * @return int converted user count + */ + public function convertUser() + { + /* Get users exist in the system. */ + $activeUsers = $this->dao + ->dbh($this->sourceDBH) + ->select("username AS account, userpassword AS password, realname, email") + ->from('BugUser') + ->orderBy('userID ASC') + ->fetchAll('account', $autoCompany = false); + + /* Get users in histories. */ + $allUsers = $this->dao->select("distinct(username) AS account")->from('BugHistory')->fetchPairs('', '', $autoCompany = false); + + /* Merge them. */ + foreach($allUsers as $key => $account) + { + if(isset($activeUsers[$account])) + { + $allUsers[$key] = $activeUsers[$account]; + } + else + { + $allUsers[$key] = array('account' => $account, 'realname' => $account, 'deleted' => '1'); + } + } + foreach($activeUsers as $account => $user) if(!isset($allUsers[$account])) $allUsers[$account] = $user; + + /* Insert into zentao. */ + $convertCount = 0; + foreach($allUsers as $account => $user) + { + if(!$this->dao->dbh($this->dbh)->findByAccount($account)->from(TABLE_USER)->fetch('account')) + { + $this->dao->dbh($this->dbh)->insert(TABLE_USER)->data($user)->exec(); + $convertCount ++; + } + else + { + self::$info['users'][] = sprintf($this->lang->convert->errorUserExists, $account); + } + } + return $convertCount; + } + + /** + * Convert project in bugfree to product in zentao. + * + * @access public + * @return int converted project count + */ + public function convertProject() + { + $projects = $this->dao->dbh($this->sourceDBH)->select("projectID AS id, projectName AS name")->from('BugProject')->fetchAll('id', $autoCompany = false); + foreach($projects as $projectID => $project) + { + unset($project->id); + $this->dao->dbh($this->dbh)->insert(TABLE_PRODUCT)->data($project)->exec(); + $this->map['product'][$projectID] = $this->dao->lastInsertID(); + } + return count($projects); + } + + /** + * Convert modules. + * + * @access public + * @return int converted modules count + */ + public function convertModule() + { + $this->map['module'][0] = 0; + $modules = $this->dao + ->dbh($this->sourceDBH) + ->select( + 'moduleID AS id, + projectID AS root, + moduleName AS name, + moduleGrade AS grade, + parentID AS parent, + "bug" AS type') + ->from('BugModule') + ->orderBy('id ASC') + ->fetchAll('id', $autoCompany = false); + foreach($modules as $moduleID => $module) + { + $module->root = $this->map['product'][$module->root]; + unset($module->id); + $this->dao->dbh($this->dbh)->insert(TABLE_MODULE)->data($module)->exec(); + $this->map['module'][$moduleID] = $this->dao->lastInsertID(); + } + + /* Update parents. */ + foreach($modules as $oldModuleID => $module) + { + $newModuleID = $this->map['module'][$oldModuleID]; + $newParentID = $this->map['module'][$module->parent]; + $this->dao->dbh($this->dbh)->update(TABLE_MODULE)->set('parent')->eq($newParentID)->where('id')->eq($newModuleID)->exec(); + } + return count($modules); + } + + /** + * Convert bugs. + * + * @access public + * @return int converted bugs count. + */ + public function convertBug() + { + $bugs = $this->dao + ->dbh($this->sourceDBH) + ->select(' + bugID AS id, + projectID AS product, + moduleID AS module, + bugTitle AS title, + bugSeverity AS severity, + bugType AS type, + bugOS AS os, + bugStatus AS status, + mailto, + openedBy, openedDate, openedBuild, + assignedTo, assignedDate, + resolvedBy, resolution, resolvedBuild, resolvedDate, + closedBy, closedDate, + lastEditedBy, lastEditedDate, + linkID as duplicateBug + ') + ->from('BugInfo') + ->orderBy('bugID') + ->fetchAll('id', $autoCompany = false); + foreach($bugs as $bugID => $bug) + { + /* Adjust some fields of bug. */ + $bugID = (int)$bugID; + unset($bug->id); + if($bug->assignedTo == 'Closed') $bug->assignedTo = 'closed'; + $bug->type = strtolower($bug->type); + $bug->os = strtolower($bug->os); + $bug->browser = 'all'; + $bug->resolution = str_replace(' ','', strtolower($bug->resolution)); + $bug->product = $this->map['product'][$bug->product]; + $bug->module = $this->map['module'][$bug->module]; + $this->dao->dbh($this->dbh)->insert(TABLE_BUG)->data($bug)->exec(); + $this->map['bug'][$bugID] = $this->dao->lastInsertID(); + } + + /* Update duplicated bugs. */ + foreach($this->map['bug'] as $oldBugID => $newBugID) + { + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('duplicateBug')->eq($newBugID)->where('duplicateBug')->eq($oldBugID)->exec(); + } + return count($bugs); + } + + /** + * Convert actions. + * + * @access public + * @return int converted actions count. + */ + public function convertAction() + { + $actions = $this->dao + ->dbh($this->sourceDBH) + ->select(" + 'bug' AS objectType, + bugID AS objectID, + userName AS actor, + action, + fullInfo AS comment, + actionDate AS date") + ->from('BugHistory') + ->orderBy('bugID, historyID') + ->fetchGroup('objectID', '', $autoCompany = false); + $convertCount = 0; + foreach($actions as $bugID => $bugActions) + { + /* Get the related bugID. */ + $bugID = (int)$bugID; + $zentaoBugID = $this->map['bug'][$bugID]; + + /* Process actions. */ + foreach($bugActions as $key => $action) + { + $action->objectID = $zentaoBugID; + if($key == 0) + { + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('steps')->eq(nl2br($action->comment))->where('id')->eq($zentaoBugID)->exec(); + $action->comment = ''; + } + $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); + $convertCount ++; + } + } + return $convertCount; + } + + /** + * Convert files. + * + * @access public + * @return int converted files count. + */ + public function convertFile() + { + $this->setPath(); + $files = $this->dao->dbh($this->sourceDBH) + ->select(" + fileName AS pathname, + fileTitle AS title, + fileType AS extension, + fileSize AS size, + 'bug' AS objectType, + bugID AS objectID, + addUser AS addedBy, + addDate AS addedDate + ") + ->from('BugFile') + ->orderBy('fileID') + ->fetchAll('', $autoCompany = false); + foreach($files as $file) + { + $file->objectID = $this->map['bug'][(int)$file->objectID]; + if(strpos($file->size, 'KB')) $file->size = (int)(str_replace('KB', '', $file->size) * 1024); + if(strpos($file->size, 'MB')) $file->size = (int)(str_replace('MB', '', $file->size) * 1024 * 1024); + $this->dao->dbh($this->dbh)->insert(TABLE_FILE)->data($file)->exec(); + + /* Copy files. */ + $soureFile = $this->filePath . $file->pathname; + if(!file_exists($soureFile)) + { + self::$info['files'][] = sprintf($this->lang->convert->errorFileNotExits, $soureFile); + continue; + } + $targetFile = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . $file->pathname; + $targetPath = dirname($targetFile); + if(!is_dir($targetPath)) mkdir($targetPath, 0777, true); + if(!copy($soureFile, $targetFile)) + { + self::$info['files'][] = sprintf($this->lang->convert->errorCopyFailed, $targetFile); + } + } + return count($files); + } +} diff --git a/trunk/module/convert/converter/bugfree2.php b/trunk/module/convert/converter/bugfree2.php new file mode 100644 index 0000000000..acac3ab1c1 --- /dev/null +++ b/trunk/module/convert/converter/bugfree2.php @@ -0,0 +1,530 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +class bugfree2ConvertModel extends bugfreeConvertModel +{ + /** + * Execute the converter. + * + * @access public + * @return array + */ + public function execute() + { + $this->clear(); + $this->setTable(); + $this->convertGroup(); + $result['users'] = $this->convertUser(); + $result['projects'] = $this->convertProject(); + $result['modules'] = $this->convertModule(); + $result['bugs'] = $this->convertBug(); + $result['cases'] = $this->convertCase(); + $result['results'] = $this->convertResult(); + $result['actions'] = $this->convertAction(); + $result['files'] = $this->convertFile(); + $this->dao->dbh($this->dbh); + $this->loadModel('tree')->fixModulePath(); + return $result; + } + + /** + * Set table names. + * + * @access public + * @return void + */ + public function setTable() + { + $dbPrefix = $this->post->dbPrefix; + define('BUGFREE_TABLE_OPTION', $dbPrefix . 'TestOptions'); + define('BUGFREE_TABLE_USER', $dbPrefix . 'TestUser'); + define('BUGFREE_TABLE_PROJECT', $dbPrefix . 'TestProject'); + define('BUGFREE_TABLE_MODULE', $dbPrefix . 'TestModule'); + define('BUGFREE_TABLE_BUGINFO', $dbPrefix . 'BugInfo'); + define('BUGFREE_TABLE_CASEINFO', $dbPrefix . 'CaseInfo'); + define('BUGFREE_TABLE_RESULTINFO', $dbPrefix . 'ResultInfo'); + define('BUGFREE_TABLE_ACTION', $dbPrefix . 'TestAction'); + define('BUGFREE_TABLE_FILE', $dbPrefix . 'TestFile'); + define('BUGFREE_TABLE_HISTORY', $dbPrefix . 'TestHistory'); + define('BUGFREE_TABLE_GROUP', $dbPrefix . 'TestGroup'); + } + + /** + * Get the version of bugfree2.x. + * + * @access public + * @return int + */ + public function getBugFreeVersion() + { + return $this->dao->dbh($this->sourceDBH) + ->select("optionValue as version")->from(BUGFREE_TABLE_OPTION) + ->where('OptionName')->eq('dbVersion') + ->fetch('version', $autoCompany = false); + + } + + /** + * Convert user. + * + * @access public + * @return int converted user count + */ + public function convertUser() + { + /* Get all user list. */ + $users = $this->dao + ->dbh($this->sourceDBH) + ->select("username AS account, userpassword AS password, realname, email, isDroped AS deleted") + ->from(BUGFREE_TABLE_USER) + ->orderBy('userID ASC') + ->fetchAll('account', $autoCompany = false); + + /* Insert into zentao. */ + $convertCount = 0; + foreach($users as $account => $user) + { + if(!$this->dao->dbh($this->dbh)->findByAccount($account)->from(TABLE_USER)->fetch('account')) + { + $this->dao->dbh($this->dbh)->insert(TABLE_USER)->data($user)->exec(); + $convertCount ++; + } + else + { + self::$info['users'][] = sprintf($this->lang->convert->errorUserExists, $account); + } + } + return $convertCount; + } + + /** + * Convert groups. + * + * @access public + * @return void converted group count. + */ + public function convertGroup() + { + if(!$this->tableExists(BUGFREE_TABLE_GROUP)) return false; + $groups = $this->dao->dbh($this->sourceDBH) + ->select("groupID AS id, groupName AS name, groupUser AS users") + ->from(BUGFREE_TABLE_GROUP) + ->fetchAll('id', $autoCompany = false); + foreach($groups as $groupID => $group) + { + /* Fix the group data. */ + if($group->name == '[All Users]') continue; + $groupUsers = explode(',', $group->users); + unset($group->id); + unset($group->users); + + /* Insert into zentao's group table. */ + $this->dao->dbh($this->dbh)->insert(TABLE_GROUP)->data($group)->exec(); + $zentaoGroupID = $this->dao->lastInsertId(); + + /* Insert into zentao's usergroup table. */ + foreach($groupUsers as $account) + { + if(empty($account)) continue; + $this->dao->dbh($this->dbh)->insert(TABLE_USERGROUP) + ->set('`group`')->eq($zentaoGroupID) + ->set('account')->eq($account) + ->exec(); + } + } + } + + /** + * Convert projects. + * + * @access public + * @return int converted projects count. + */ + public function convertProject() + { + $projects = $this->dao->dbh($this->sourceDBH) + ->select("projectID AS id, projectName AS name, isDroped AS deleted") + ->from(BUGFREE_TABLE_PROJECT) + ->fetchAll('id', $autoComapny = false); + foreach($projects as $projectID => $project) + { + unset($project->id); + $this->dao->dbh($this->dbh)->insert(TABLE_PRODUCT)->data($project)->exec(); + $this->map['product'][$projectID] = $this->dao->lastInsertID(); + } + return count($projects); + } + + /** + * Convert modules. + * + * @access public + * @return int converted modules count. + */ + public function convertModule() + { + $this->map['module'][0] = 0; + $modules = $this->dao + ->dbh($this->sourceDBH) + ->select( + 'moduleID AS id, + moduleType as type, + projectID AS root, + moduleName AS name, + moduleGrade AS grade, + parentID AS parent, + displayOrder AS `order`') + ->from(BUGFREE_TABLE_MODULE) + ->orderBy('id ASC') + ->fetchAll('id', $autoCompany = false); + foreach($modules as $moduleID => $module) + { + $module->root = $this->map['product'][$module->root]; + $module->type = strtolower($module->type); + unset($module->id); + $this->dao->dbh($this->dbh)->insert(TABLE_MODULE)->data($module)->exec(); + $this->map['module'][$moduleID] = $this->dao->lastInsertID(); + } + + /* Update parent. */ + foreach($modules as $oldModuleID => $module) + { + $newModuleID = $this->map['module'][$oldModuleID]; + $newParentID = $this->map['module'][$module->parent]; + $this->dao->dbh($this->dbh)->update(TABLE_MODULE)->set('parent')->eq($newParentID)->where('id')->eq($newModuleID)->exec(); + } + return count($modules); + } + + /** + * Convert bugs. + * + * @access public + * @return int converted bugs count. + */ + public function convertBug() + { + $bugs = $this->dao + ->dbh($this->sourceDBH) + ->select(' + bugID AS id, + projectID AS product, + moduleID AS module, + bugTitle AS title, + bugSeverity AS severity, + bugPriority AS pri, + bugType AS type, + bugOS AS os, + bugBrowser AS browser, + bugMachine AS hardware, + howFound AS found, + reproSteps AS steps, + bugStatus AS status, + linkID AS linkBug, + duplicateID AS duplicateBug, + caseID AS `case`, + 1 AS caseVersion, + resultID AS result, + mailto, + openedBy, openedDate, openedBuild, + assignedTo, assignedDate, + resolvedBy, resolution, resolvedBuild, resolvedDate, + closedBy, closedDate, + lastEditedBy, lastEditedDate, + bugKeyword AS keywords + ') + ->from(BUGFREE_TABLE_BUGINFO) + ->where('isDroped')->eq(0) + ->orderBy('bugID') + ->fetchAll('id', $autoCompany = false); + foreach($bugs as $bugID => $bug) + { + /* Fix some fileds of bug. */ + $bugID = (int)$bugID; + unset($bug->id); + + if($bug->assignedTo == 'Closed') $bug->assignedTo = 'closed'; + if($bug->assignedTo == 'Active') $bug->assignedTo = ''; + + $bug->type = strtolower($bug->type); + $bug->found = strtolower($bug->found); + $bug->status = strtolower($bug->status); + $bug->os = strtolower($bug->os); + $bug->browser= strtolower($bug->browser); + $bug->steps = nl2br($bug->steps); + + if($bug->os == 'winvista') $bug->os = 'vista'; + if($bug->browser == 'firefox3.0') $bug->browser = 'firefox3'; + if($bug->browser == 'firefox2.0') $bug->browser = 'firefox2'; + if($bug->openedBuild == 'N/A') $bug->openedBuild = ''; + if(!$bug->case) $bug->caseVersion = 0; + + $bug->resolution = str_replace(' ', '', strtolower($bug->resolution)); + $bug->product = $this->map['product'][$bug->product]; + $bug->module = $this->map['module'][$bug->module]; + $this->dao->dbh($this->dbh)->insert(TABLE_BUG)->data($bug)->exec(); + $this->map['bug'][$bugID] = $this->dao->lastInsertID(); + } + + /* Update duplicated bugs. */ + foreach($this->map['bug'] as $oldBugID => $newBugID) + { + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('duplicateBug')->eq($newBugID)->where('duplicateBug')->eq($oldBugID)->exec(); + } + return count($bugs); + } + + /** + * Convert cases. + * + * @access public + * @return int converted cases count. + */ + public function convertCase() + { + $cases = $this->dao + ->dbh($this->sourceDBH) + ->select(' + caseID AS id, + projectID AS product, + moduleID AS module, + caseTitle AS title, + caseSteps AS step, + casePriority AS pri, + caseType AS type, + caseStatus AS status, + caseMethod AS howRun, + casePlan AS stage, + openedBy, openedDate, + lastEditedBy, lastEditedDate, + scriptedBy, scriptedDate, scriptStatus, scriptLocation, + linkID AS linkCase, + casekeyword AS keywords, + 1 AS version, + bugID + ') + ->from(BUGFREE_TABLE_CASEINFO) + ->where('isDroped')->eq(0) + ->orderBy('caseID') + ->fetchAll('id', $autoCompany = false); + foreach($cases as $caseID => $case) + { + /* Fix fields of case. */ + $caseID = (int)$caseID; + $step = $case->step; + $bugs = explode(',', $case->bugID); + unset($case->id); + unset($case->step); + unset($case->bugID); + + $case->type = strtolower($case->type); + $case->status = strtolower($case->status); + $case->howRun = strtolower($case->howRun); + $case->stage = strtolower($case->stage); + + if($case->type == 'configuration') $case->type = 'config'; + if($case->type == 'setup') $case->type = 'install'; + if($case->type == 'functional') $case->type = 'feature'; + if($case->status == 'active') $case->status = 'normal'; + + /* Change product and module by zentao's product and module. */ + $case->product = $this->map['product'][$case->product]; + $case->module = $this->map['module'][$case->module]; + + /* Insert into case table. */ + $this->dao->dbh($this->dbh)->insert(TABLE_CASE)->data($case)->exec(); + $zentaoCaseID = $this->dao->lastInsertID(); + $this->map['case'][$caseID] = $zentaoCaseID; + + /* Insert into case step table. */ + $caseStep->case = $zentaoCaseID; + $caseStep->version = 1; + $caseStep->desc = $step; + $this->dao->dbh($this->dbh)->insert(TABLE_CASESTEP)->data($caseStep)->exec(); + + /* Update related bugs. */ + foreach($bugs as $bugID) + { + if(!isset($this->map['bug'][$bugID])) continue; + $zentaoBugID = $this->map['bug'][$bugID]; + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('`case`')->eq($zentaoCaseID)->where('id')->eq($zentaoBugID)->limit(1)->exec(); + } + } + return count($cases); + } + + /** + * Convert results. + * + * @access public + * @return int converted results count. + */ + public function convertResult() + { + $results = $this->dao->dbh($this->sourceDBH) + ->select(' + resultID AS id, + caseID AS `case`, + resultValue AS caseResult, + 1 AS version, + openedDate as date, + bugID + ') + ->from(BUGFREE_TABLE_RESULTINFO) + ->orderBy('id') + ->fetchAll('id', $autoCompany = false); + foreach($results as $resultID => $result) + { + unset($result->id); + + /* The bug id of zentao. */ + $bugID = (int)$result->bugID; + $zentaoBugID = $this->map['bug'][$bugID]; + unset($result->bugID); + + /* Insert into test result table. */ + $this->dao->dbh($this->dbh)->insert(TABLE_TESTRESULT)->data($result)->exec(); + $zentaoResultID = $this->dao->lastInsertId(); + $this->map['result'][$resultID] = $zentaoResultID; + + /* Update result table. */ + $this->dao->dbh($this->dbh)->update(TABLE_BUG)->set('result')->eq($zentaoResultID)->where('id')->eq($zentaoBugID)->limit(1)->exec(); + } + return count($results); + } + + /** + * Convert actions. + * + * @access public + * @return int converted actions count. + */ + public function convertAction() + { + $actions = $this->dao + ->dbh($this->sourceDBH) + ->select("actionID AS id, + actionTarget AS objectType, + idValue AS objectID, + actionUser AS actor, + actionType AS action, + actionDate AS date, + actionNote AS comment + ") + ->from(BUGFREE_TABLE_ACTION) + ->where('actionTarget' != 'Result') + ->orderBy('actionID') + ->fetchAll('id', $autoComapny = false); + + foreach($actions as $actionID => $action) + { + $actionID = (int)$action->id; + unset($action->id); + $action->objectType = strtolower($action->objectType); + $action->action = strtolower($action->action); + $action->objectID = $this->map[$action->objectType][$action->objectID]; + + $this->dao->dbh($this->dbh)->insert(TABLE_ACTION)->data($action)->exec(); + $this->map['action'][$actionID] = $this->dao->lastInsertID(); + } + return count($actions); + } + + /** + * Convert histories. + * + * @access public + * @return int the converted histories count. + */ + public function convertHistory() + { + $histories = $this->dao->dbh($this->sourceDBH) + ->select('actioID, actionField AS field, oldValue AS old, newValue AS new') + ->from(BUGFREE_TABLE_HISTORY) + ->orderBy('historyID') + ->fetchAll('', $autoCompany = false); + foreach($histories as $history) + { + $history->actionID = $this->map['action'][$history->actionID]; + $this->dao->dbh($this->dbh)->insert(TABLE_HISTORY)->data($history)->exec(); + } + } + + /** + * Convert attachments. + * + * @access public + * @return int the converted files count. + */ + public function convertFile() + { + $this->setPath(); + $files = $this->dao->dbh($this->sourceDBH) + ->select(" + actionID, + fileName AS pathname, + fileTitle AS title, + fileType AS extension, + fileSize AS size + ") + ->from(BUGFREE_TABLE_FILE) + ->orderBy('fileID') + ->fetchAll('', $autoCompany = false); + foreach($files as $file) + { + /* Get the actionID in zentao, to get file info. */ + $zentaoActionID = $this->map['action'][$file->actionID]; + $zentaoAction = $this->dao->dbh($this->dbh)->findById($zentaoActionID)->from(TABLE_ACTION)->fetch(); + $file->objectType = $zentaoAction->objectType; + $file->objectID = $zentaoAction->objectID; + $file->addedBy = $zentaoAction->actor; + $file->addedDate = $zentaoAction->date; + unset($file->actionID); + + /* Compute the file size. */ + if(strpos($file->size, 'KB')) $file->size = (int)(str_replace('KB', '', $file->size) * 1024); + if(strpos($file->size, 'MB')) $file->size = (int)(str_replace('MB', '', $file->size) * 1024 * 1024); + + /* Insert into database. */ + $this->dao->dbh($this->dbh)->insert(TABLE_FILE)->data($file)->exec(); + + /* Copy file. */ + $soureFile = $this->filePath . $file->pathname; + if(!file_exists($soureFile)) + { + self::$info['files'][] = sprintf($this->lang->convert->errorFileNotExits, $soureFile); + continue; + } + $targetFile = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . $file->pathname; + $targetPath = dirname($targetFile); + if(!is_dir($targetPath)) mkdir($targetPath, 0777, true); + if(!copy($soureFile, $targetFile)) + { + self::$info['files'][] = sprintf($this->lang->convert->errorCopyFailed, $targetFile); + } + } + return count($files); + } + + /** + * Clear the converted records. + * + * @access public + * @return void + */ + public function clear() + { + foreach($this->session->state as $table => $maxID) + { + $this->dao->dbh($this->dbh)->delete()->from($table)->where('id')->gt($maxID)->exec(); + } + } +} diff --git a/trunk/module/convert/converter/redmine.php b/trunk/module/convert/converter/redmine.php new file mode 100644 index 0000000000..50c1b0207a --- /dev/null +++ b/trunk/module/convert/converter/redmine.php @@ -0,0 +1,88 @@ + + * @package convert + * @version $Id $ + * @link http://www.zentao.net + */ +class redmineConvertModel extends convertModel +{ + public $map = array(); + public $filePath = ''; + static public $info = array(); + + /** + * Connect to db auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + parent::connectDB(); + } + + /** + * Check table. + * + * @access public + * @return bool + */ + public function checkTables() + { + return true; + } + + /** + * Check the install path. + * + * @access public + * @return bool + */ + public function checkPath() + { + $this->setPath(); + return file_exists($this->filePath); + } + + /** + * Set the path of attachments. + * + * @access public + * @return bool + */ + public function setPath() + { + $this->filePath = realpath($this->post->installPath) . $this->app->getPathFix() . 'files' . $this->app->getPathFix(); + } + + /** + * Excute the convert. + * + * @param int $version + * @access public + * @return void + */ + public function execute($version) + { + } + + /** + * Clear rows added in converting. + * + * @access public + * @return void + */ + public function clear() + { + foreach($this->session->state as $table => $maxID) + { + $this->dao->dbh($this->dbh)->delete()->from($table)->where('id')->gt($maxID)->exec(); + } + } +} diff --git a/trunk/module/convert/converter/redmine1.1.php b/trunk/module/convert/converter/redmine1.1.php new file mode 100644 index 0000000000..87fec9d407 --- /dev/null +++ b/trunk/module/convert/converter/redmine1.1.php @@ -0,0 +1,822 @@ + + * @package convert + * @version $Id $ + * @link http://www.zentao.net + */ +class redmine11ConvertModel extends redmineConvertModel +{ + static $convertGroupCount = 0; + static $convertUserCount = 0; + static $convertProductCount = 0; + static $convertProjectCount = 0; + static $convertStoryCount = 0; + static $convertTaskCount = 0; + static $convertBugCount = 0; + static $convertProductPlanCount = 0; + static $convertTeamCount = 0; + static $convertReleaseCount = 0; + static $convertBuildCount = 0; + static $convertDocLibCount = 0; + static $convertDocCount = 0; + static $convertFileCount = 0; + public $issueType; + + public function __construct($issueType) + { + parent::__construct(); + $this->issueType = $issueType; + } + /** + * Execute the converter. + * + * @access public + * @return array + */ + public function execute() + { + $this->clear(); + $this->setTable(); + $this->convertGroup(); + $this->convertUser(); + $this->convertUserGroup(); + $this->convertProduct(); + $this->convertProject(); + $this->convertBuildAndRelease(); + $this->convertProductPlan(); + $this->convertProjectProduct(); + $this->convertTeam(); + $this->convertDocLib(); + $this->convertDoc(); + $this->convertNews(); + $this->convertIssue(); + $this->convertFile(); + $this->dao->dbh($this->dbh); + $this->loadModel('tree')->fixModulePath(); + + $result['groups'] = self::$convertGroupCount; + $result['users'] = self::$convertUserCount ; + $result['products'] = self::$convertProductCount ; + $result['projects'] = self::$convertProjectCount ; + $result['stories'] = self::$convertStoryCount; + $result['tasks'] = self::$convertTaskCount ; + $result['bugs'] = self::$convertBugCount ; + $result['productPlans'] = self::$convertProductPlanCount; + $result['teams'] = self::$convertTeamCount; + $result['releases'] = self::$convertReleaseCount; + $result['builds'] = self::$convertBuildCount; + $result['docLibs'] = self::$convertDocLibCount ; + $result['docs'] = self::$convertDocCount; + $result['files'] = self::$convertFileCount; + return $result; + } + + /** + * Set table names. + * + * @access public + * @return void + */ + public function setTable() + { + //$dbPrefix = $this->post->dbPrefix; + $dbPrefix = ''; + define('REDMINE_TABLE_ATTACHMENTS', $dbPrefix . 'attachments'); + define('REDMINE_TABLE_AUTH_SOURCES', $dbPrefix . 'auth_sources'); + define('REDMINE_TABLE_BOARDS', $dbPrefix . 'boards'); + define('REDMINE_TABLE_CHANGES', $dbPrefix . 'changes'); + define('REDMINE_TABLE_CHANGESETS', $dbPrefix . 'changesets'); + define('REDMINE_TABLE_CHANGESETS_ISSUES', $dbPrefix . 'changesets_issues'); + define('REDMINE_TABLE_COMMENTS', $dbPrefix . 'comments'); + define('REDMINE_TABLE_CUSTOM_FIELDS', $dbPrefix . 'custom_fields'); + define('REDMINE_TABLE_CUSTOM_FIELDS_PROJECTS', $dbPrefix . 'custom_fields_projects'); + define('REDMINE_TABLE_CUSTOM_FIELDS_TRACKERS', $dbPrefix . 'custom_fields_trackers'); + define('REDMINE_TABLE_CUSTOM_VALUES', $dbPrefix . 'custom_values'); + define('REDMINE_TABLE_DOCUMENTS', $dbPrefix . 'documents'); + define('REDMINE_TABLE_ENABLED_MODULES', $dbPrefix . 'enabled_modules'); + define('REDMINE_TABLE_ENUMERATIONS', $dbPrefix . 'enumerations'); + define('REDMINE_TABLE_GROUPS_USERS', $dbPrefix . 'groups_users'); + define('REDMINE_TABLE_ISSUES', $dbPrefix . 'issues'); + define('REDMINE_TABLE_ISSUE_CATEGORIES', $dbPrefix . 'issue_categories'); + define('REDMINE_TABLE_ISSUE_RELATIONS', $dbPrefix . 'issue_relations'); + define('REDMINE_TABLE_ISSUE_STATUSES', $dbPrefix . 'issue_statuses'); + define('REDMINE_TABLE_JOURNALS', $dbPrefix . 'journals'); + define('REDMINE_TABLE_JOURNAL_DETAILS', $dbPrefix . 'journal_details'); + define('REDMINE_TABLE_MEMBERS', $dbPrefix . 'members'); + define('REDMINE_TABLE_MEMBER_ROLES', $dbPrefix . 'member_roles'); + define('REDMINE_TABLE_MESSAGES', $dbPrefix . 'messages'); + define('REDMINE_TABLE_NEWS', $dbPrefix . 'news'); + define('REDMINE_TABLE_OPEN_ID_AUTHENTICATION_ASSOCIATIONS', $dbPrefix . 'open_id_authentication_associations'); + define('REDMINE_TABLE_OPEN_ID_AUTHENTICATION_NONCES', $dbPrefix . 'open_id_authentication_nonces'); + define('REDMINE_TABLE_PROJECTS', $dbPrefix . 'projects'); + define('REDMINE_TABLE_PROJECTS_TRACKERS', $dbPrefix . 'projects_trackers'); + define('REDMINE_TABLE_QUERIES', $dbPrefix . 'queries'); + define('REDMINE_TABLE_REPOSITORIES', $dbPrefix . 'repositories'); + define('REDMINE_TABLE_ROLES', $dbPrefix . 'roles'); + define('REDMINE_TABLE_SCHEMA_MIGRATIONS', $dbPrefix . 'schema_migrations'); + define('REDMINE_TABLE_SETTINGS', $dbPrefix . 'settings'); + define('REDMINE_TABLE_TIME_ENTRIES', $dbPrefix . 'time_entries'); + define('REDMINE_TABLE_TOKENS', $dbPrefix . 'tokens'); + define('REDMINE_TABLE_TRACKERS', $dbPrefix . 'trackers'); + define('REDMINE_TABLE_USERS', $dbPrefix . 'users'); + define('REDMINE_TABLE_USER_PREFERENCES', $dbPrefix . 'user_preferences'); + define('REDMINE_TABLE_VERSIONS', $dbPrefix . 'versions'); + define('REDMINE_TABLE_WATCHERS', $dbPrefix . 'watchers'); + define('REDMINE_TABLE_WIKIS', $dbPrefix . 'wikis'); + define('REDMINE_TABLE_WIKI_CONTENTS', $dbPrefix . 'wiki_contents'); + define('REDMINE_TABLE_WIKI_CONTENT_VERSIONS', $dbPrefix . 'wiki_content_versions'); + define('REDMINE_TABLE_WIKI_PAGES', $dbPrefix . 'wiki_pages'); + define('REDMINE_TABLE_WIKI_REDIRECTS', $dbPrefix . 'wiki_redirects'); + define('REDMINE_TABLE_WORKFLOWS', $dbPrefix . 'workflows'); + } + + /** + * Convert groups. + * + * @access public + * @return void + */ + public function convertGroup() + { + /* Get group list */ + $groups = $this->dao->dbh($this->sourceDBH) + ->select("id, lastName AS name") + ->from(REDMINE_TABLE_USERS) + ->where('type')->eq('Group') + ->fetchAll('id', $autoCompany = false); + + $zentaoGroupNames = $this->dao->dbh($this->dbh)->select('id, name')->from(TABLE_GROUP)->fetchPairs(); + $zentaoGroupIDs = $this->dao->dbh($this->dbh)->select('name, id')->from(TABLE_GROUP)->fetchPairs(); + + /* Insert into zentao */ + $convertCount = 0; + foreach($groups as $groupID =>$group) + { + unset($group->id); + if(in_array("$group->name", $zentaoGroupNames)) + { + self::$info['groups'][] = sprintf($this->lang->convert->errorGroupExists, $group->name); + $this->map['groups'][$groupID] = $zentaoGroupIDs[$group->name]; + } + else + { + $this->dao->dbh($this->dbh)->insert(TABLE_GROUP) + ->data($group)->exec(); + $this->map['groups'][$groupID] = $this->dao->lastInsertID(); + $convertCount ++; + } + } + self::$convertGroupCount += $convertCount; + } + + /** + * Convert users. + * + * @access public + * @return void + */ + public function convertUser() + { + /* Get user list. */ + $users = $this->dao->dbh($this->sourceDBH) + ->select("id, login AS account, firstname, lastname, mail as email") + ->from(REDMINE_TABLE_USERS) + ->where('type')->eq('User') + ->fetchAll('id', $autoCompany = false); + + $zentaoUserNames = $this->dao->dbh($this->dbh)->select('id, account')->from(TABLE_USER)->fetchPairs(); + $zentaoUserIDs = $this->dao->dbh($this->dbh)->select('account, id')->from(TABLE_USER)->fetchPairs(); + + /* Insert into zentao. */ + $convertCount = 0; + foreach($users as $id => $user) + { + if(in_array("$user->account", $zentaoUserNames)) + { + self::$info['users'][] = sprintf($this->lang->convert->errorUserExists, $user->account); + $this->map['users'][$id] = $zentaoUserIDs[$user->account]; + } + else + { + $user->password = md5('123456'); + $user->realname = $user->lastname . $user->firstname; + unset($user->id); + unset($user->lastname); + unset($user->firstname); + $this->dao->dbh($this->dbh)->insert(TABLE_USER)->data($user)->exec(); + $this->map['users'][$id] = $this->dao->lastInsertID(); + $convertCount ++; + } + } + self::$convertUserCount += $convertCount; + } + + /** + * convert relationship between user and group. + * + * @access public + * @return void + */ + public function convertUserGroup() + { + $this->map['groups'][0] = 0; + /* Get relation between user and group list. */ + $userGroups = $this->dao->dbh($this->sourceDBH) + ->select("t1.group_id, t2.login as account") + ->from(REDMINE_TABLE_GROUPS_USERS)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.user_id = t2.id') + ->fetchAll('', $autoCompany = false); + + $zentaoUserGroups = $this->dao->dbh($this->dbh)->select('*')->from(TABLE_USERGROUP)->fetchAll(); + + /* Insert into zentao. */ + $userGroupExist = false; + foreach($userGroups as $userGroup) + { + $userGroup->group = $this->map['groups'][$userGroup->group_id]; + unset($userGroup->group_id); + foreach($zentaoUserGroups as $zentaoUserGroup) + { + if($userGroup->group == $zentaoUserGroup->group and $userGroup->account == $zentaoUserGroup->account) + { + $userGroupExist = true; + } + } + if($userGroupExist == false) + { + $this->dao->dbh($this->dbh)->insert(TABLE_USERGROUP)->data($userGroup)->exec(); + } + } + } + + /** + * convert products. + * + * @access public + * @return void + */ + public function convertProduct() + { + /* Get product list */ + $products = $this->dao->dbh($this->sourceDBH) + ->select("id, name, description as `desc`, created_on as createdDate") + ->from(REDMINE_TABLE_PROJECTS) + ->fetchAll('id', $autoComapny = false); + + /* Insert into zentao */ + foreach($products as $productID => $product) + { + unset($product->id); + $this->dao->dbh($this->dbh)->insert(TABLE_PRODUCT)->data($product)->exec(); + $this->map['products'][$productID] = $this->dao->lastInsertID(); + } + self::$convertProductCount += count($products); + } + + /** + * Convert projects. + * + * @access public + * @return void + */ + public function convertProject() + { + /* Get project list */ + $projects = $this->dao->dbh($this->sourceDBH) + ->select("id, name, project_id, description as `desc`, effective_date AS end") + ->from(REDMINE_TABLE_VERSIONS) + ->fetchAll('id', $autoComapny = false); + + /* Insert into zentao */ + foreach($projects as $projectID => $project) + { + $productID = $project->project_id; + unset($project->id); + unset($project->project_id); + $this->dao->dbh($this->dbh)->insert(TABLE_PROJECT)->data($project)->exec(); + $this->map['projects'][$productID][$projectID] = $this->dao->lastInsertID(); + $this->map['project'][$projectID] = $this->map['projects'][$productID][$projectID]; + } + + /* Create a same name project with product */ + foreach($this->map['products'] as $productID) + { + $project = $this->dao->dbh($this->dbh)->select('name')->from(TABLE_PRODUCT)->where('id')->eq($productID)->fetch(); + $this->dao->dbh($this->dbh)->insert(TABLE_PROJECT)->data($project)->exec(); + $this->map['projectOfProduct'][$productID] = $this->dao->lastinsertID(); + } + $convertCount = count($projects) + count($this->map['projectOfProduct']); + self::$convertProjectCount += $convertCount; + } + + /** + * convert builds and releases + * + * @access public + * @return void + */ + public function convertBuildAndRelease() + { + /* Get build list */ + $buildAndReleases = $this->dao->dbh($this->sourceDBH) + ->select("id, name, project_id, description as `desc`, effective_date AS date") + ->from(REDMINE_TABLE_VERSIONS) + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + $convertBuildsCount = 0; + $convertReleasesCount = 0; + $zentaoBuildNames = $this->dao->dbh($this->dbh)->select('id, name')->from(TABLE_BUILD)->fetchPairs(); + $zentaoBuildIDs = $this->dao->dbh($this->dbh)->select('name, id')->from(TABLE_BUILD)->fetchPairs(); + $zentaoReleaseNames = $this->dao->dbh($this->dbh)->select('id, name')->from(TABLE_RELEASE)->fetchPairs(); + $zentaoReleaseIDs = $this->dao->dbh($this->dbh)->select('name, id')->from(TABLE_RELEASE)->fetchPairs(); + foreach($buildAndReleases as $id => $buildAndRelease) + { + $buildAndRelease->project = $this->map['project'][$id]; + $buildAndRelease->product = $this->map['products'][$buildAndRelease->project_id]; + unset($buildAndRelease->id); + unset($buildAndRelease->project_id); + + if(in_array($buildAndRelease->name, $zentaoBuildNames)) + { + self::$info['builds'][] = sprintf($this->lang->convert->errorBuildExists, $buildAndRelease->name); + $buildAndRelease->build = $zentaoBuildIDs[$buildAndRelease->name]; + } + else + { + $this->dao->dbh($this->dbh)->insert(TABLE_BUILD)->data($buildAndRelease)->exec(); + $buildAndRelease->build = $this->dao->lastInsertID(); + $convertBuildsCount ++; + } + + unset($buildAndRelease->project); + if(in_array($buildAndRelease->name, $zentaoBuildNames)) + { + self::$info['releases'][] = sprintf($this->lang->convert->errorReleaseExists, $buildAndRelease->name); + } + else + { + $this->dao->dbh($this->dbh)->insert(TABLE_RELEASE)->data($buildAndRelease)->exec(); + $convertReleasesCount ++; + } + } + self::$convertBuildCount += $convertBuildsCount; + self::$convertReleaseCount += $convertReleasesCount; + } + + /** + * convert productPlans + * + * @access public + * @return void + */ + public function convertProductPlan() + { + /* Get productPlan list */ + $productPlans = $this->dao->dbh($this->sourceDBH) + ->select("id, name as title, project_id, description as `desc`, effective_date as end, created_on AS begin") + ->from(REDMINE_TABLE_VERSIONS) + ->fetchAll('id', $autoCompany = false); + /* Insert into zentao */ + foreach($productPlans as $id => $productPlan) + { + $productPlan->product = $this->map['products'][$productPlan->project_id]; + unset($productPlan->id); + unset($productPlan->project_id); + $this->dao->dbh($this->dbh)->insert(TABLE_PRODUCTPLAN)->data($productPlan)->exec(); + } + + /* Create a same plan with product */ + foreach($this->map['products'] as $productID) + { + $productPlan = $this->dao->dbh($this->dbh)->select("name as title, createdDate as begin")->from(TABLE_PRODUCT)->where('id')->eq($productID)->fetch(); + $productPlan->product = $productID; + $this->dao->dbh($this->dbh)->insert(TABLE_PRODUCTPLAN)->data($productPlan)->exec(); + $this->map['planOfProduct'][$productID] = $this->dao->lastinsertID(); + } + $convertCount = count($this->map['products']) + count($productPlans); + self::$convertProductPlanCount += $convertCount; + } + + /** + * convert relationship between project and product. + * + * @access public + * @return void + */ + public function convertProjectProduct() + { + foreach($this->map['projects'] as $productID => $projects) + { + foreach($projects as $projectID => $project) + { + $this->dao->dbh($this->dbh)->insert(TABLE_PROJECTPRODUCT) + ->set('project')->eq($project) + ->set('product')->eq($this->map['products'][$productID]) + ->exec(); + } + } + } + + /** + * convert teams. + * + * @access public + * @return void + */ + public function convertTeam() + { + /* Get team list */ + $teams = $this->dao->dbh($this->sourceDBH) + ->select("t2.login as account, t1.project_id, t1.created_on AS joinDate") + ->from(REDMINE_TABLE_MEMBERS)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.user_id = t2.id') + ->where('t2.type')->eq('User') + ->fetchAll('', $autoCompany = false); + + /* Insert into zentao */ + foreach($teams as $team) + { + $productID = $team->project_id; + unset($team->project_id); + foreach($this->map['projects'][$productID] as $projectID) + { + $team->project = $projectID; + $this->dao->dbh($this->dbh)->insert(TABLE_TEAM)->data($team)->exec(); + } + } + self::$convertTeamCount += count($teams); + } + + /** + * convert docLibs. + * + * @access public + * @return void + */ + public function convertDocLib() + { + /* Get docLib list */ + $docLibs = $this->dao->dbh($this->sourceDBH) + ->select("id, name")->from(REDMINE_TABLE_ENUMERATIONS) + ->where('type')->eq('DocumentCategory') + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + foreach($docLibs as $docLibID => $docLib) + { + unset($docLib->id); + $this->dao->dbh($this->dbh)->insert(TABLE_DOCLIB)->data($docLib)->exec(); + $this->map['docLibs'][$docLibID] = $this->dao->lastInsertID(); + } + self::$convertDocLibCount += count($docLibs); + } + + /** + * convert docs. + * + * @access public + * @return void + */ + public function convertDoc() + { + /* Get all docs */ + $docs = $this->dao->dbh($this->sourceDBH) + ->select("t1.id, t1.project_id AS product, t2.id AS lib, t1.title, t1.description AS content, t1.created_on AS addedDate") + ->from(REDMINE_TABLE_DOCUMENTS)->alias('t1') + ->leftjoin(REDMINE_TABLE_ENUMERATIONS)->alias('t2')->on('t1.category_id = t2.id') + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + foreach($docs as $docID => $doc) + { + unset($doc->id); + $doc->type = 'text'; + $doc->project = 0; + $doc->product = $this->map['products'][$doc->product]; + $doc->lib = $this->map['docLibs'][$doc->lib]; + $this->dao->dbh($this->dbh)->insert(TABLE_DOC)->data($doc)->exec(); + $this->map['docs'][$docID] = $this->dao->lastInsertID(); + } + self::$convertDocCount += count($docs); + } + + /** + * convert news. + * + * @access public + * @return void + */ + public function convertNews() + { + /* Get news from redmine */ + $news = $this->dao->dbh($this->sourceDBH) + ->select("t1.project_id as product, t1.title, t1.summary as digest, t1.description as content, t2.login as addedBy, t1.created_on as addedDate") + ->from(REDMINE_TABLE_NEWS)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.author_id = t2.id') + ->fetchAll('', $autoCompany = false); + + /* Create a news docLib */ + $newLib->name = 'news'; + $this->dao->dbh($this->dbh)->insert(TABLE_DOCLIB)->data($newLib)->exec(); + $this->map['news'] = $this->dao->lastInsertID(); + self::$convertDocLibCount += 1; + + /* Insert into zentao */ + foreach($news as $new) + { + $new->product = $this->map['products'][$new->product]; + $new->project = 0; + $new->lib = $this->map['news']; + $new->type = 'text'; + + $this->dao->dbh($this->dbh)->insert(TABLE_DOC)->data($new)->exec(); + } + self::$convertDocCount += count($news); + } + + /** + * convert issue + * + * @param array $aimTypes //aimTypes['issueTypeID'] = aimtype eg. aimTypes[1] = 'bug'; + * @param array $statusTypes //statusTypes['task']['statusTypeID'] = statusType eg. statusTypes['task'][1] = 'wait'; + * //statusTypes['bug']['statusTypeID'] = statusType eg. statusTypes['bug'][1] = 'active'; + * @param array $priTypes //priTypes['task']['priTypeID'] = priType; eg. priTypes['task'][1] = 1; + * @access public + * @return void + */ + public function convertIssue() + { + $aimTypes = $this->issueType->aimTypes; + $statusTypes = $this->issueType->statusTypes; + $priTypes = $this->issueType->priTypes; + + foreach($aimTypes as $issueType => $aimType) + { + if('story' == $aimType) + { + $this->convertStory($issueType, $statusTypes, $priTypes); + } + elseif('task' == $aimType) + { + $this->convertTask($issueType, $statusTypes, $priTypes); + } + else + { + $this->convertBug($issueType, $statusTypes, $priTypes); + } + } + } + + /** + * convert story + * + * @param array $issueType + * @param array $statusTypes + * @param array $priTypes + * @access public + * @return void + */ + public function convertStory($issueType, $statusTypes, $priTypes) + { + /* Get story list*/ + $stories = $this->dao->dbh($this->sourceDBH) + ->select("t1.id, t1.project_id as product, t1.subject as title, t1.description as spec, t1.status_id as status, t2.login as assignedTo, t1.priority_id as pri, t3.login as openedBy, t1.created_on as openedDate, t1.estimated_hours as estimate, t1.updated_on as lastEditedDate") + ->from(REDMINE_TABLE_ISSUES)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.assigned_to_id = t2.id') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t3')->on('t1.author_id = t3.id') + ->where('t1.tracker_id')->eq($issueType) + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + foreach($stories as $issueID => $story) + { + $storySpec->title = $story->title; + $storySpec->spec = $story->spec; + unset($story->id); + unset($story->spec); + + /* Insert story into table story */ + $story->product = $this->map['products'][$story->product]; + $story->module = 0; + $story->plan = $this->map['planOfProduct'][$story->product]; + $story->fromBug = 0; + $story->pri = $priTypes['story'][$story->pri]; + $story->status = $statusTypes['story'][$story->status]; + $story->toBug = 0; + $story->duplicateStory = 0; + $this->dao->dbh($this->dbh)->insert(TABLE_STORY)->data($story)->exec(); + $this->map['issueID'][$issueID] = $this->dao->lastInsertID(); + $this->map['issueType'][$issueID] = 'story'; + + /* Insert data into table storySpec */ + $storySpec->story = $this->map['issueID'][$issueID]; + $storySpec->version = 1; + $this->dao->dbh($this->dbh)->insert(TABLE_STORYSPEC)->data($storySpec)->exec(); + } + self::$convertStoryCount += count($stories); + } + + /** + * convert task + * + * @param array $issueType + * @param array $statusTypes + * @param array $priTypes + * @access public + * @return void + */ + public function convertTask($issueType, $statusTypes, $priTypes) + { + /* Get task list */ + $tasks = $this->dao->dbh($this->sourceDBH) + ->select("t1.id, t1.project_id as product, t1.fixed_version_id as project, t1.subject as name, t1.description as `desc`, t1.due_date as deadline, t1.status_id as status, t2.login as assignedTo, t1.priority_id as pri, t3.login as openedBy, t1.created_on as openedDate, t1.estimated_hours as estimate, t1.updated_on as lastEditedDate") + ->from(REDMINE_TABLE_ISSUES)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.assigned_to_id = t2.id') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t3')->on('t1.author_id = t3.id') + ->where('t1.tracker_id')->eq($issueType) + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + foreach($tasks as $issueID => $task) + { + $task->story = 0; + $task->storyVersion = 0; + $task->type = 'misc'; + $task->pri = $priTypes['task'][$task->pri]; + $task->status = $statusTypes['task'][$task->status]; + if($task->project == 0) + { + $task->project = $this->map['projectOfProduct'][$task->product]; + } + else + { + $task->project = $this->map['project'][$task->project]; + } + unset($task->id); + unset($task->product); + $this->dao->dbh($this->dbh)->insert(TABLE_TASK)->data($task)->exec(); + $this->map['issueID'][$issueID] = $this->dao->lastInsertID(); + $this->map['issueType'][$issueID] = 'task'; + } + self::$convertTaskCount += count($tasks); + } + + /** + * convert bug + * + * @param array $issueType + * @param array $statusTypes + * @param array $priTypes + * @access public + * @return void + */ + public function convertBug($issueType, $statusTypes, $priTypes) + { + /* Get bug list */ + $bugs = $this->dao->dbh($this->sourceDBH) + ->select("t1.id, t1.project_id as product, t1.fixed_version_id project, t1.subject as title, t1.description as steps, t1.status_id as status, t2.login as assignedTo, t1.priority_id as pri, t3.login as openedBy, t1.created_on as openedDate, t1.updated_on as lastEditedDate") + ->from(REDMINE_TABLE_ISSUES)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.assigned_to_id = t2.id') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t3')->on('t1.author_id = t3.id') + ->where('t1.tracker_id')->eq($issueType) + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + foreach($bugs as $issueID => $bug) + { + $bug->product = $this->map['products'][$bug->product]; + $bug->module = 0; + $bug->story = 0; + $bug->storyVersion = 1; + $bug->task = 0; + $bug->severity = 3; + $bug->type = 'others'; + $bug->status = $statusTypes['bug'][$bug->status]; + $bug->openedBuild = 'trunk'; + $bug->duplicateBug = 0; + $bug->case = 0; + $bug->caseVersion = 1; + $bug->result = 0; + if($bug->project == 0) + { + $bug->project = $this->map['projectOfProduct'][$bug->product]; + } + else + { + $bug->project = $this->map['project'][$bug->project]; + } + unset($bug->id); + $this->dao->dbh($this->dbh)->insert(TABLE_BUG)->data($bug)->exec(); + $this->map['issueID'][$issueID] = $this->dao->lastInsertID(); + $this->map['issueType'][$issueID] = 'bug'; + } + self::$convertBugCount += count($bugs); + } + + /** + * Convert attachments. + * + * @access public + * @return void + */ + public function convertFile() + { + $this->setPath(); + + /* Get file list */ + $files = $this->dao->dbh($this->sourceDBH) + ->select('t1.id, t1.container_id as objectID, t1.container_type as objectType, t1.filename as title, t1.disk_filename as pathname, t1.filesize as size, t2.login as addedBy, t1.created_on as addedDate, description') + ->from(REDMINE_TABLE_ATTACHMENTS)->alias('t1') + ->leftJoin(REDMINE_TABLE_USERS)->alias('t2')->on('t1.author_id = t2.id') + ->fetchAll('id', $autoCompany = false); + + /* Insert into zentao */ + foreach($files as $fileID => $file) + { + if($file->description != '') + { + $file->title = $file->description; + unset($file->description); + } + else + { + unset($file->description); + } + + /* Transform objectType and objectID */ + if($file->objectType == 'Issue') + { + $file->objectType = $this->map['issueType'][$file->objectID]; + $file->objectID = $this->map['issueID'][$file->objectID]; + } + elseif($file->objectType == 'Document') + { + $file->objectType = 'doc' ; + $file->objectID = $this->map['docs'][$file->objectID]; + } + elseif($file->objectType == 'WikiPage') + { + continue; + } + elseif($file->objectType == 'Version') + { + $doc->project = $this->map['project'][$file->objectID]; + $doc = $this->dao->dbh($this->dbh)->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($doc->project)->fetch(); + $doc->lib = 'project'; + $doc->module = 0; + $doc->title = $file->title; + $doc->type = 'file'; + $doc->addedBy = $file->addedBy; + $doc->addedDate = $file->addedDate; + $this->dao->dbh($this->dbh)->insert(TABLE_DOC)->data($doc)->exec(); + self::$convertDocCount += 1; + + $file->objectType = 'doc'; + $file->objectID = $this->dao->lastInsertID(); + } + + $pathname = pathinfo($file->pathname); + $file->extension = $pathname["extension"]; + unset($file->id); + + /* Insert into database. */ + $this->dao->dbh($this->dbh)->insert(TABLE_FILE)->data($file)->exec(); + + /* Copy file. */ + $soureFile = $this->filePath . $file->pathname; + if(!file_exists($soureFile)) + { + self::$info['files'][] = sprintf($this->lang->convert->errorFileNotExits, $soureFile); + continue; + } + $targetFile = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . $file->pathname; + $targetPath = dirname($targetFile); + if(!is_dir($targetPath)) mkdir($targetPath, 0777, true); + if(!copy($soureFile, $targetFile)) + { + self::$info['files'][] = sprintf($this->lang->convert->errorCopyFailed, $targetFile); + } + } + self::$convertFileCount += count($files); + } + + /** + * Clear the converted records. + * + * @access public + * @return void + */ + public function clear() + { + foreach($this->session->state as $table => $maxID) + { + $this->dao->dbh($this->dbh)->delete()->from($table)->where('id')->gt($maxID)->exec(); + } + } +} diff --git a/trunk/module/convert/js/selectsource.js b/trunk/module/convert/js/selectsource.js new file mode 100644 index 0000000000..43061492f9 --- /dev/null +++ b/trunk/module/convert/js/selectsource.js @@ -0,0 +1 @@ +function setForm(){} diff --git a/trunk/module/convert/js/setconfig.js b/trunk/module/convert/js/setconfig.js new file mode 100644 index 0000000000..43061492f9 --- /dev/null +++ b/trunk/module/convert/js/setconfig.js @@ -0,0 +1 @@ +function setForm(){} diff --git a/trunk/module/convert/lang/en.php b/trunk/module/convert/lang/en.php new file mode 100644 index 0000000000..e30b32019f --- /dev/null +++ b/trunk/module/convert/lang/en.php @@ -0,0 +1,109 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->convert->common = 'Import'; +$lang->convert->next = 'Next'; +$lang->convert->pre = 'Back'; +$lang->convert->reload = 'Reload'; +$lang->convert->error = 'Error '; + +$lang->convert->start = 'Begin import'; +$lang->convert->desc = <<Welcome to use this convert wizard which will help you to import other system data to ZenTaoPMS.

      +Importing is dangerous. Be sure to backup your database and other data files and sure nobody is using pms when importing. +EOT; + +$lang->convert->selectSource = 'Select source system and version'; +$lang->convert->source = 'Source system'; +$lang->convert->version = 'Version'; +$lang->convert->mustSelectSource = "Must select a source system"; + +$lang->convert->direction = 'Please select the direction of issue in Redmine'; +$lang->convert->questionTypeOfRedmine = 'Types of issue in Redmine'; +$lang->convert->aimTypeOfZentao = 'Aim type in Zentao'; + +$lang->convert->directionList['bug'] = 'Bug'; +$lang->convert->directionList['task'] = 'Task'; +$lang->convert->directionList['story'] = 'Story'; + +$lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); +$lang->convert->sourceList['Redmine'] = array('Redmine_1_1' => '1.1'); + +$lang->convert->setting = 'Setting'; +$lang->convert->checkConfig = 'Check setting'; + +$lang->convert->ok = 'Check passed(√)'; +$lang->convert->fail = 'Check failed(×)'; + +$lang->convert->settingDB = 'Set database'; +$lang->convert->dbHost = 'Database server'; +$lang->convert->dbPort = 'Server port'; +$lang->convert->dbUser = 'Database user'; +$lang->convert->dbPassword = 'Database password'; +$lang->convert->dbName = '%s database'; +$lang->convert->dbCharset = '%s charset'; +$lang->convert->dbPrefix = '%s table prefix'; +$lang->convert->installPath = '%s installed path'; + +$lang->convert->checkDB = 'Database'; +$lang->convert->checkTable = 'Table'; +$lang->convert->checkPath = 'Installed path'; + +$lang->convert->execute = 'Execute import'; +$lang->convert->item = 'Imported items'; +$lang->convert->count = 'Count'; +$lang->convert->info = 'Info'; + +$lang->convert->bugfree->users = 'User'; +$lang->convert->bugfree->projects = 'Project'; +$lang->convert->bugfree->modules = 'Module'; +$lang->convert->bugfree->bugs = 'Bug'; +$lang->convert->bugfree->cases = 'Case'; +$lang->convert->bugfree->results = 'Result'; +$lang->convert->bugfree->actions = 'History'; +$lang->convert->bugfree->files = 'File'; + +$lang->convert->redmine->users = 'Users'; +$lang->convert->redmine->groups = 'Groups'; +$lang->convert->redmine->products = 'Products'; +$lang->convert->redmine->projects = 'Projects'; +$lang->convert->redmine->stories = 'Stories'; +$lang->convert->redmine->tasks = 'Tasks'; +$lang->convert->redmine->bugs = 'Bugs'; +$lang->convert->redmine->productPlans = 'ProductPlans'; +$lang->convert->redmine->teams = 'Teams'; +$lang->convert->redmine->releases = 'Releases'; +$lang->convert->redmine->builds = 'Builds'; +$lang->convert->redmine->docLibs = 'DocLibs'; +$lang->convert->redmine->docs = 'Docs'; +$lang->convert->redmine->files = 'files'; + +$lang->convert->errorConnectDB = 'Connect to database server failed.'; +$lang->convert->errorFileNotExits = 'File %s not exits.'; +$lang->convert->errorUserExists = 'User %s exits already.'; +$lang->convert->errorGroupExists = 'Group %s exits already.'; +$lang->convert->errorBuildExists = 'Build %s exits already.'; +$lang->convert->errorReleaseExists = 'Release %s exits already.'; +$lang->convert->errorCopyFailed = 'file %s copy failed.'; + +$lang->convert->setParam = 'Please set params'; + +$lang->convert->aimType = 'Issue types goto'; +$lang->convert->statusType->bug = 'Status types goto(status of Bug)'; +$lang->convert->statusType->story = 'Status types goto(status of story)'; +$lang->convert->statusType->task = 'Status types goto(status of task)'; +$lang->convert->priType->bug = 'Priority types goto(priority of Bug)'; +$lang->convert->priType->story = 'Priority types goto(priority of story)'; +$lang->convert->priType->task = 'Priority types goto(priority of task)'; + +$lang->convert->issue->redmine = 'Redmine'; +$lang->convert->issue->zentao = 'ZenTao'; +$lang->convert->issue->goto = 'Goto'; diff --git a/trunk/module/convert/lang/zh-cn.php b/trunk/module/convert/lang/zh-cn.php new file mode 100644 index 0000000000..fc24c9a19b --- /dev/null +++ b/trunk/module/convert/lang/zh-cn.php @@ -0,0 +1,109 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->convert->common = '从其他系统导入'; +$lang->convert->next = '下一步'; +$lang->convert->pre = '返回'; +$lang->convert->reload = '刷新'; +$lang->convert->error = '错误 '; + +$lang->convert->start = '开始转换'; +$lang->convert->desc = <<欢迎使用系统转换向导,本程序会帮助您将其他系统的数据转换到禅道项目管理系统中。

      +转换存在一定的风险,转换之前,我们强烈建议您备份数据库及相应的数据文件,并保证转换的时候,没有其他人进行操作。 +EOT; + +$lang->convert->selectSource = '选择来源系统及版本'; +$lang->convert->source = '来源系统'; +$lang->convert->version = '版本'; +$lang->convert->mustSelectSource = "必须选择一个来源。"; + +$lang->convert->direction = '请选择项目问题转换方向'; +$lang->convert->questionTypeOfRedmine = 'Redmine中问题类型'; +$lang->convert->aimTypeOfZentao = '转化为Zentao中的类型'; + +$lang->convert->directionList['bug'] = 'Bug'; +$lang->convert->directionList['task'] = '任务'; +$lang->convert->directionList['story'] = '需求'; + +$lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); +$lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); + +$lang->convert->setting = '设置'; +$lang->convert->checkConfig = '检查配置'; + +$lang->convert->ok = '检查通过(√)'; +$lang->convert->fail = '检查失败(×)'; + +$lang->convert->settingDB = '设置数据库'; +$lang->convert->dbHost = '数据库服务器'; +$lang->convert->dbPort = '服务器端口'; +$lang->convert->dbUser = '数据库用户名'; +$lang->convert->dbPassword = '数据库密码'; +$lang->convert->dbName = '%s使用的库'; +$lang->convert->dbCharset = '%s数据库编码'; +$lang->convert->dbPrefix = '%s表前缀'; +$lang->convert->installPath = '%s安装的根目录'; + +$lang->convert->checkDB = '数据库'; +$lang->convert->checkTable = '表'; +$lang->convert->checkPath = '安装路径'; + +$lang->convert->execute = '执行转换'; +$lang->convert->item = '转换项'; +$lang->convert->count = '转换数量'; +$lang->convert->info = '转换信息'; + +$lang->convert->bugfree->users = '用户'; +$lang->convert->bugfree->projects = '项目'; +$lang->convert->bugfree->modules = '模块'; +$lang->convert->bugfree->bugs = 'Bug'; +$lang->convert->bugfree->cases = '测试用例'; +$lang->convert->bugfree->results = '测试结果'; +$lang->convert->bugfree->actions = '历史记录'; +$lang->convert->bugfree->files = '附件'; + +$lang->convert->redmine->users = '用户'; +$lang->convert->redmine->groups = '用户分组'; +$lang->convert->redmine->products = '产品'; +$lang->convert->redmine->projects = '项目'; +$lang->convert->redmine->stories = '需求'; +$lang->convert->redmine->tasks = '任务'; +$lang->convert->redmine->bugs = 'Bug'; +$lang->convert->redmine->productPlans = '产品计划'; +$lang->convert->redmine->teams = '团队'; +$lang->convert->redmine->releases = '发布'; +$lang->convert->redmine->builds = 'Build'; +$lang->convert->redmine->docLibs = '文档库'; +$lang->convert->redmine->docs = '文档'; +$lang->convert->redmine->files = '附件'; + +$lang->convert->errorConnectDB = '数据库连接失败 '; +$lang->convert->errorFileNotExits = '文件 %s 不存在'; +$lang->convert->errorUserExists = '用户 %s 已存在'; +$lang->convert->errorGroupExists = '分组 %s 已存在'; +$lang->convert->errorBuildExists = 'Build %s 已存在'; +$lang->convert->errorReleaseExists = '发布 %s 已存在'; +$lang->convert->errorCopyFailed = '文件 %s 拷贝失败'; + +$lang->convert->setParam = '请设置转换参数'; + +$lang->convert->aimType = '问题类型转换'; +$lang->convert->statusType->bug = '状态类型转换(Bug状态)'; +$lang->convert->statusType->story = '状态类型转换(Story状态)'; +$lang->convert->statusType->task = '状态类型转换(Task状态)'; +$lang->convert->priType->bug = '优先级类型转换(Bug状态)'; +$lang->convert->priType->story = '优先级类型转换(Story状态)'; +$lang->convert->priType->task = '优先级类型转换(Task状态)'; + +$lang->convert->issue->redmine = 'Redmine'; +$lang->convert->issue->zentao = '禅道'; +$lang->convert->issue->goto = '转换为'; diff --git a/trunk/module/convert/lang/zh-tw.php b/trunk/module/convert/lang/zh-tw.php new file mode 100644 index 0000000000..6a58a07183 --- /dev/null +++ b/trunk/module/convert/lang/zh-tw.php @@ -0,0 +1,109 @@ + + * @package convert + * @version $Id: zh-tw.php 2605 2012-02-21 07:22:58Z wwccss $ + * @link http://www.zentao.net + */ +$lang->convert->common = '從其他系統導入'; +$lang->convert->next = '下一步'; +$lang->convert->pre = '返回'; +$lang->convert->reload = '刷新'; +$lang->convert->error = '錯誤 '; + +$lang->convert->start = '開始轉換'; +$lang->convert->desc = <<歡迎使用系統轉換嚮導,本程序會幫助您將其他系統的數據轉換到禪道項目管理系統中。

      +轉換存在一定的風險,轉換之前,我們強烈建議您備份資料庫及相應的數據檔案,並保證轉換的時候,沒有其他人進行操作。 +EOT; + +$lang->convert->selectSource = '選擇來源系統及版本'; +$lang->convert->source = '來源系統'; +$lang->convert->version = '版本'; +$lang->convert->mustSelectSource = "必須選擇一個來源。"; + +$lang->convert->direction = '請選擇項目問題轉換方向'; +$lang->convert->questionTypeOfRedmine = 'Redmine中問題類型'; +$lang->convert->aimTypeOfZentao = '轉化為Zentao中的類型'; + +$lang->convert->directionList['bug'] = 'Bug'; +$lang->convert->directionList['task'] = '任務'; +$lang->convert->directionList['story'] = '需求'; + +$lang->convert->sourceList['BugFree'] = array('bugfree_1' => '1.x', 'bugfree_2' => '2.x'); +$lang->convert->sourceList['Redmine'] = array('Redmine_1.1' => '1.1'); + +$lang->convert->setting = '設置'; +$lang->convert->checkConfig = '檢查配置'; + +$lang->convert->ok = '檢查通過(√)'; +$lang->convert->fail = '檢查失敗(×)'; + +$lang->convert->settingDB = '設置資料庫'; +$lang->convert->dbHost = '資料庫伺服器'; +$lang->convert->dbPort = '伺服器連接埠'; +$lang->convert->dbUser = '資料庫用戶名'; +$lang->convert->dbPassword = '資料庫密碼'; +$lang->convert->dbName = '%s使用的庫'; +$lang->convert->dbCharset = '%s資料庫編碼'; +$lang->convert->dbPrefix = '%s表首碼'; +$lang->convert->installPath = '%s安裝的根目錄'; + +$lang->convert->checkDB = '資料庫'; +$lang->convert->checkTable = '表'; +$lang->convert->checkPath = '安裝路徑'; + +$lang->convert->execute = '執行轉換'; +$lang->convert->item = '轉換項'; +$lang->convert->count = '轉換數量'; +$lang->convert->info = '轉換信息'; + +$lang->convert->bugfree->users = '用戶'; +$lang->convert->bugfree->projects = '項目'; +$lang->convert->bugfree->modules = '模組'; +$lang->convert->bugfree->bugs = 'Bug'; +$lang->convert->bugfree->cases = '測試用例'; +$lang->convert->bugfree->results = '測試結果'; +$lang->convert->bugfree->actions = '歷史記錄'; +$lang->convert->bugfree->files = '附件'; + +$lang->convert->redmine->users = '用戶'; +$lang->convert->redmine->groups = '用戶分組'; +$lang->convert->redmine->products = '產品'; +$lang->convert->redmine->projects = '項目'; +$lang->convert->redmine->stories = '需求'; +$lang->convert->redmine->tasks = '任務'; +$lang->convert->redmine->bugs = 'Bug'; +$lang->convert->redmine->productPlans = '產品計劃'; +$lang->convert->redmine->teams = '團隊'; +$lang->convert->redmine->releases = '發佈'; +$lang->convert->redmine->builds = 'Build'; +$lang->convert->redmine->docLibs = '文檔庫'; +$lang->convert->redmine->docs = '文檔'; +$lang->convert->redmine->files = '附件'; + +$lang->convert->errorConnectDB = '資料庫連接失敗 '; +$lang->convert->errorFileNotExits = '檔案 %s 不存在'; +$lang->convert->errorUserExists = '用戶 %s 已存在'; +$lang->convert->errorGroupExists = '分組 %s 已存在'; +$lang->convert->errorBuildExists = 'Build %s 已存在'; +$lang->convert->errorReleaseExists = '發佈 %s 已存在'; +$lang->convert->errorCopyFailed = '檔案 %s 拷貝失敗'; + +$lang->convert->setParam = '請設置轉換參數'; + +$lang->convert->aimType = '問題類型轉換'; +$lang->convert->statusType->bug = '狀態類型轉換(Bug狀態)'; +$lang->convert->statusType->story = '狀態類型轉換(Story狀態)'; +$lang->convert->statusType->task = '狀態類型轉換(Task狀態)'; +$lang->convert->priType->bug = '優先順序類型轉換(Bug狀態)'; +$lang->convert->priType->story = '優先順序類型轉換(Story狀態)'; +$lang->convert->priType->task = '優先順序類型轉換(Task狀態)'; + +$lang->convert->issue->redmine = 'Redmine'; +$lang->convert->issue->zentao = '禪道'; +$lang->convert->issue->goto = '轉換為'; diff --git a/trunk/module/convert/model.php b/trunk/module/convert/model.php new file mode 100644 index 0000000000..507c41841f --- /dev/null +++ b/trunk/module/convert/model.php @@ -0,0 +1,94 @@ + + * @package convert + * @version $Id$ + * @link http://www.zentao.net + */ +?> +post->dbHost}; port={$this->post->dbPort};dbname={$this->post->dbName}"; + try + { + $dbh = new PDO($dsn, $this->post->dbUser, $this->post->dbPassword); + $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $dbh->exec("SET NAMES {$this->post->dbCharset}"); + $this->sourceDBH = $dbh; + return $dbh; + } + catch (PDOException $exception) + { + return $exception->getMessage(); + } + } + + /** + * Check database exits or not. + * + * @access public + * @return bool + */ + public function dbExists() + { + $sql = "SHOW DATABASES like '{$this->post->db->name}'"; + return $this->dbh->query($sql)->fetch(); + } + + /** + * Check table exits or not. + * + * @access public + * @return bool + */ + public function tableExists($table) + { + $sql = "SHOW tables like '$table'"; + return $this->dbh->query($sql)->fetch(); + } + + /** + * Save the max id of every table. Thus when we convert again, when can delete id larger then the saved max id. + * + * @access public + * @return void + */ + public function saveState() + { + /* Get user defined tables. */ + $constants = get_defined_constants(true); + $userConstants = $constants['user']; + + /* These tables needn't save. */ + unset($userConstants['TABLE_BURN']); + unset($userConstants['TABLE_GROUPPRIV']); + unset($userConstants['TABLE_PROJECTPRODUCT']); + unset($userConstants['TABLE_PROJECTSTORY']); + unset($userConstants['TABLE_STORYSPEC']); + unset($userConstants['TABLE_TEAM']); + unset($userConstants['TABLE_USERGROUP']); + + /* Get max id of every table. */ + foreach($userConstants as $key => $value) + { + if(strpos($key, 'TABLE') === false) continue; + if($key == 'TABLE_COMPANY') continue; + $state[$value] = (int)$this->dao->select('MAX(id) AS id')->from($value)->fetch('id'); + } + $this->session->set('state', $state); + } +} diff --git a/trunk/module/convert/view/checkbugfree.html.php b/trunk/module/convert/view/checkbugfree.html.php new file mode 100644 index 0000000000..6ba86cc1ee --- /dev/null +++ b/trunk/module/convert/view/checkbugfree.html.php @@ -0,0 +1,32 @@ +
      convert->checkDB;?>convert->ok) : printf($lang->convert->fail);?>
      convert->checkPath;?>convert->ok) : printf($lang->convert->fail);?>
      + convert->execute); + } + else + { + echo html::commonButton($lang->goback, 'onclick=history.back();'); + } +?> +
      + + +
      convert->checkConfig . $lang->colon . strtoupper($source);?>
      +post->dbHost); +echo html::hidden('dbPort', $this->post->dbPort); +echo html::hidden('dbUser', $this->post->dbUser); +echo html::hidden('dbPassword', $this->post->dbPassword); +echo html::hidden('dbName', $this->post->dbName); +echo html::hidden('dbCharset', $this->post->dbCharset); +echo html::hidden('dbPrefix', $this->post->dbPrefix); +echo html::hidden('installPath',$this->post->installPath); +?> + + diff --git a/trunk/module/convert/view/checkredmine.html.php b/trunk/module/convert/view/checkredmine.html.php new file mode 100644 index 0000000000..5d66dbf9ea --- /dev/null +++ b/trunk/module/convert/view/checkredmine.html.php @@ -0,0 +1,154 @@ + + convert->checkDB;?> + convert->ok) : printf($lang->convert->fail);?> + + + convert->checkPath;?> + convert->ok) : printf($lang->convert->fail);?> + + + + + + + + + + + + + + + + + + +
      convert->setParam; ?>
      + + + + + + + + $tracker):?> + + + + + + +
      convert->aimType; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?>
      +
      + + + + + + + + $status):?> + + + + + + +
      convert->statusType->bug; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?> bug->statusList);?>
      +
      + + + + + + + + $status):?> + + + + + + +
      convert->statusType->story; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?> story->statusList);?>
      +
      + + + + + + + + $status):?> + + + + + + +
      convert->statusType->task; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?> task->statusList);?>
      +
      + + + + + + + + $pri):?> + + + + + + +
      convert->priType->bug; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?> bug->priList);?>
      +
      + + + + + + + + $pri):?> + + + + + + +
      convert->priType->story; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?> story->priList);?>
      +
      + + + + + + + + $pri):?> + + + + + + +
      convert->priType->task; ?>
      convert->issue->redmine; ?> convert->issue->goto; ?> convert->issue->zentao; ?>
      name?> " ?> task->priList);?>
      +
      + + + + + convert->execute); + } + else + { + echo html::commonButton($lang->goback, 'onclick=history.back();'); + } +?> + + diff --git a/trunk/module/convert/view/convertIssue.html.php b/trunk/module/convert/view/convertIssue.html.php new file mode 100644 index 0000000000..a1b8ef2c43 --- /dev/null +++ b/trunk/module/convert/view/convertIssue.html.php @@ -0,0 +1,21 @@ + + +
      + + + + + + + + + + + + + + + +
      convert->direction;?>
      convert->questionTypeOfRedmine;?>convert->aimTypeOfZentao;?>
      name;?>name", $lang->convert->directionList, '', "class='select-3'");?>
      +
      + diff --git a/trunk/module/convert/view/convertbugfree.html.php b/trunk/module/convert/view/convertbugfree.html.php new file mode 100644 index 0000000000..414ae5fb4b --- /dev/null +++ b/trunk/module/convert/view/convertbugfree.html.php @@ -0,0 +1,49 @@ + + convert->item?> + convert->count?> + convert->info?> + + + convert->bugfree->users;?> + + ', $info['users']);?> + + + convert->bugfree->projects;?> + + ', $info['projects']);?> + + + convert->bugfree->modules;?> + + ', $info['modules']);?> + + + convert->bugfree->bugs;?> + + ', $info['bugs']);?> + + 1):?> + + convert->bugfree->cases;?> + + ', $info['cases']);?> + + + convert->bugfree->results;?> + + ', $info['results']);?> + + + + + + convert->bugfree->actions;?> + + ', $info['actions']);?> + + + convert->bugfree->files;?> + + ', $info['files']);?> + diff --git a/trunk/module/convert/view/convertredmine.html.php b/trunk/module/convert/view/convertredmine.html.php new file mode 100644 index 0000000000..0a4302fc6e --- /dev/null +++ b/trunk/module/convert/view/convertredmine.html.php @@ -0,0 +1,75 @@ + + convert->item?> + convert->count?> + convert->info?> + + + convert->redmine->users;?> + + ', $info['users']);?> + + + convert->redmine->groups;?> + + ', $info['groups']);?> + + + convert->redmine->products;?> + + ', $info['products']);?> + + + convert->redmine->projects;?> + + ', $info['projects']);?> + + + convert->redmine->stories;?> + + ', $info['stories']);?> + + + convert->redmine->tasks;?> + + ', $info['tasks']);?> + + + convert->redmine->bugs;?> + + ', $info['bugs']);?> + + + convert->redmine->productPlans;?> + + ', $info['productPlans']);?> + + + convert->redmine->teams;?> + + ', $info['teams']);?> + + + convert->redmine->releases;?> + + ', $info['releases']);?> + + + convert->redmine->builds;?> + + ', $info['builds']);?> + + + convert->redmine->docLibs;?> + + ', $info['docLibs']);?> + + + convert->redmine->docs;?> + + ', $info['docs']);?> + + + convert->redmine->files;?> + + ', $info['files']);?> + diff --git a/trunk/module/convert/view/execute.html.php b/trunk/module/convert/view/execute.html.php new file mode 100644 index 0000000000..6667ec2f3a --- /dev/null +++ b/trunk/module/convert/view/execute.html.php @@ -0,0 +1,18 @@ + + * @package convert + * @version $Id$ + */ +?> + + + + +
      convert->execute . $lang->colon . strtoupper($source);?>
      + + diff --git a/trunk/module/convert/view/index.html.php b/trunk/module/convert/view/index.html.php new file mode 100644 index 0000000000..f1743268bd --- /dev/null +++ b/trunk/module/convert/view/index.html.php @@ -0,0 +1,18 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + + + + +
      convert->common;?>
      convert->desc);?>

      createLink('convert', 'selectsource'), $lang->convert->start);?>

      + diff --git a/trunk/module/convert/view/selectsource.html.php b/trunk/module/convert/view/selectsource.html.php new file mode 100644 index 0000000000..8a3a1bab7d --- /dev/null +++ b/trunk/module/convert/view/selectsource.html.php @@ -0,0 +1,31 @@ + + * @package convert + * @version $Id$ + */ +?> + +
      '> + + + + + + + convert->sourceList as $name => $versions):?> + + + + + + + + +
      convert->selectSource;?>
      convert->source;?>convert->version;?>
      +
      + diff --git a/trunk/module/convert/view/setbugfree.html.php b/trunk/module/convert/view/setbugfree.html.php new file mode 100644 index 0000000000..098dd629ed --- /dev/null +++ b/trunk/module/convert/view/setbugfree.html.php @@ -0,0 +1,34 @@ + + convert->dbHost;?> + db->host, "class='text-3'");?> + + + convert->dbPort;?> + db->port, "class='text-3'");?> + + + convert->dbUser;?> + db->user, "class='text-3'");?> + + + convert->dbPassword;?> + db->password, "class='text-3'");?> + + + convert->dbName, $source);?> + + + + convert->dbCharset, $source);?> + + + 1):?> + + convert->dbPrefix, $source);?> + + + + + convert->installPath, $source);?> + + diff --git a/trunk/module/convert/view/setconfig.html.php b/trunk/module/convert/view/setconfig.html.php new file mode 100644 index 0000000000..f2b4621078 --- /dev/null +++ b/trunk/module/convert/view/setconfig.html.php @@ -0,0 +1,23 @@ + + * @package convert + * @version $Id$ + */ +?> + +
      '> + + + + + + +
      convert->setting . $lang->colon . strtoupper($source);?>
      + +
      + diff --git a/trunk/module/convert/view/setredmine.html.php b/trunk/module/convert/view/setredmine.html.php new file mode 100644 index 0000000000..fd459f6e03 --- /dev/null +++ b/trunk/module/convert/view/setredmine.html.php @@ -0,0 +1,28 @@ + + convert->dbHost;?> + db->host, "class='text-3'");?> + + + convert->dbPort;?> + db->port, "class='text-3'");?> + + + convert->dbUser;?> + db->user, "class='text-3'");?> + + + convert->dbPassword;?> + db->password, "class='text-3'");?> + + + convert->dbName, $source);?> + + + + convert->dbCharset, $source);?> + + + + convert->installPath, $source);?> + + diff --git a/trunk/module/dept/control.php b/trunk/module/dept/control.php new file mode 100644 index 0000000000..3313db4cd8 --- /dev/null +++ b/trunk/module/dept/control.php @@ -0,0 +1,101 @@ + + * @package dept + * @version $Id$ + * @link http://www.zentao.net + */ +class dept extends control +{ + const NEW_CHILD_COUNT = 5; + + /** + * Construct function, set menu. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('company')->setMenu(); + } + + /** + * Browse a department. + * + * @param int $deptID + * @access public + * @return void + */ + public function browse($deptID = 0) + { + $header['title'] = $this->lang->dept->manage . $this->lang->colon . $this->app->company->name; + $position[] = $this->lang->dept->manage; + + $parentDepts = $this->dept->getParents($deptID); + $this->view->header = $header; + $this->view->position = $position; + $this->view->deptID = $deptID; + $this->view->depts = $this->dept->getTreeMenu($rootDeptID = 0, array('deptmodel', 'createManageLink')); + $this->view->parentDepts = $parentDepts; + $this->view->sons = $this->dept->getSons($deptID); + $this->display(); + } + + /** + * Update the departments order. + * + * @access public + * @return void + */ + public function updateOrder() + { + if(!empty($_POST)) + { + $this->dept->updateOrder($_POST['orders']); + die(js::reload('parent')); + } + } + + /** + * Manage childs. + * + * @access public + * @return void + */ + public function manageChild() + { + if(!empty($_POST)) + { + $this->dept->manageChild($_POST['parentDeptID'], $_POST['depts']); + die(js::reload('parent')); + } + } + + /** + * Delete a department. + * + * @param int $deptID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($deptID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm($this->lang->dept->confirmDelete, $this->createLink('dept', 'delete', "deptID=$deptID&confirm=yes")); + exit; + } + else + { + $this->dept->delete($deptID); + die(js::reload('parent')); + } + } +} diff --git a/trunk/module/dept/lang/en.php b/trunk/module/dept/lang/en.php new file mode 100644 index 0000000000..00d716d8b6 --- /dev/null +++ b/trunk/module/dept/lang/en.php @@ -0,0 +1,23 @@ + + * @package dept + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->dept->common = 'Department'; +$lang->dept->add = "Add"; +$lang->dept->addChild = "Add child department"; +$lang->dept->manageChild = "Child deprtment"; +$lang->dept->delete = "Delete"; +$lang->dept->browse = "Manage"; +$lang->dept->manage = "Manage"; +$lang->dept->updateOrder = "Update order"; +$lang->dept->users = "Users"; + +$lang->dept->saveButton = " Save (S) "; +$lang->dept->confirmDelete = "Are you sure to delete this department?"; diff --git a/trunk/module/dept/lang/zh-cn.php b/trunk/module/dept/lang/zh-cn.php new file mode 100644 index 0000000000..b5e808f4e4 --- /dev/null +++ b/trunk/module/dept/lang/zh-cn.php @@ -0,0 +1,23 @@ + + * @package dept + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->dept->common = '部门结构'; +$lang->dept->add = "添加"; +$lang->dept->addChild = "添加下级部门"; +$lang->dept->manageChild = "下级部门"; +$lang->dept->delete = "删除部门"; +$lang->dept->browse = "部门维护"; +$lang->dept->manage = "维护部门结构"; +$lang->dept->updateOrder = "更新排序"; +$lang->dept->users = "成员列表"; + +$lang->dept->saveButton = " 保存 (S) "; +$lang->dept->confirmDelete = " 您确定删除该部门吗?"; diff --git a/trunk/module/dept/lang/zh-tw.php b/trunk/module/dept/lang/zh-tw.php new file mode 100644 index 0000000000..cff26d52b7 --- /dev/null +++ b/trunk/module/dept/lang/zh-tw.php @@ -0,0 +1,23 @@ + + * @package dept + * @version $Id: zh-tw.php 2605 2012-02-21 07:22:58Z wwccss $ + * @link http://www.zentao.net + */ +$lang->dept->common = '部門結構'; +$lang->dept->add = "添加"; +$lang->dept->addChild = "添加下級部門"; +$lang->dept->manageChild = "下級部門"; +$lang->dept->delete = "刪除部門"; +$lang->dept->browse = "部門維護"; +$lang->dept->manage = "維護部門結構"; +$lang->dept->updateOrder = "更新排序"; +$lang->dept->users = "成員列表"; + +$lang->dept->saveButton = " 保存 (S) "; +$lang->dept->confirmDelete = " 您確定刪除該部門嗎?"; diff --git a/trunk/module/dept/model.php b/trunk/module/dept/model.php new file mode 100644 index 0000000000..f355eb8785 --- /dev/null +++ b/trunk/module/dept/model.php @@ -0,0 +1,302 @@ + + * @package dept + * @version $Id$ + * @link http://www.zentao.net + */ +?> +dao->findById($deptID)->from(TABLE_DEPT)->fetch(); + } + + /** + * Build the query. + * + * @param int $rootDeptID + * @access public + * @return string + */ + public function buildMenuQuery($rootDeptID) + { + $rootDept = $this->getByID($rootDeptID); + if(!$rootDept) $rootDept->path = ''; + return $this->dao->select('*')->from(TABLE_DEPT) + ->beginIF($rootDeptID > 0)->where('path')->like($rootDept->path . '%')->fi() + ->orderBy('grade desc, `order`') + ->get(); + } + + /** + * Get option menu of departments. + * + * @param int $rootDeptID + * @access public + * @return array + */ + public function getOptionMenu($rootDeptID = 0) + { + $deptMenu = array(); + $stmt = $this->dbh->query($this->buildMenuQuery($rootDeptID)); + $depts = array(); + while($dept = $stmt->fetch()) $depts[$dept->id] = $dept; + + foreach($depts as $dept) + { + $parentDepts = explode(',', $dept->path); + $deptName = '/'; + foreach($parentDepts as $parentDeptID) + { + if(empty($parentDeptID)) continue; + $deptName .= $depts[$parentDeptID]->name . '/'; + } + $deptName = rtrim($deptName, '/'); + $deptName .= "|$dept->id\n"; + + if(isset($deptMenu[$dept->id]) and !empty($deptMenu[$dept->id])) + { + if(isset($deptMenu[$dept->parent])) + { + $deptMenu[$dept->parent] .= $deptName; + } + else + { + $deptMenu[$dept->parent] = $deptName;; + } + $deptMenu[$dept->parent] .= $deptMenu[$dept->id]; + } + else + { + if(isset($deptMenu[$dept->parent]) and !empty($deptMenu[$dept->parent])) + { + $deptMenu[$dept->parent] .= $deptName; + } + else + { + $deptMenu[$dept->parent] = $deptName; + } + } + } + + $topMenu = @array_pop($deptMenu); + $topMenu = explode("\n", trim($topMenu)); + $lastMenu[] = '/'; + foreach($topMenu as $menu) + { + if(!strpos($menu, '|')) continue; + list($label, $deptID) = explode('|', $menu); + $lastMenu[$deptID] = $label; + } + return $lastMenu; + } + + /** + * Get the treemenu of departments. + * + * @param int $rootDeptID + * @param string $userFunc + * @access public + * @return string + */ + public function getTreeMenu($rootDeptID = 0, $userFunc) + { + $deptMenu = array(); + $stmt = $this->dbh->query($this->buildMenuQuery($rootDeptID)); + while($dept = $stmt->fetch()) + { + $linkHtml = call_user_func($userFunc, $dept); + + if(isset($deptMenu[$dept->id]) and !empty($deptMenu[$dept->id])) + { + if(!isset($deptMenu[$dept->parent])) $deptMenu[$dept->parent] = ''; + $deptMenu[$dept->parent] .= "
    • $linkHtml"; + $deptMenu[$dept->parent] .= "
        ".$deptMenu[$dept->id]."
      \n"; + } + else + { + if(isset($deptMenu[$dept->parent]) and !empty($deptMenu[$dept->parent])) + { + $deptMenu[$dept->parent] .= "
    • $linkHtml\n"; + } + else + { + $deptMenu[$dept->parent] = "
    • $linkHtml\n"; + } + } + $deptMenu[$dept->parent] .= "
    • \n"; + } + + $lastMenu = "
        " . @array_pop($deptMenu) . "
      \n"; + return $lastMenu; + } + + /** + * Create the manage link. + * + * @param int $dept + * @access public + * @return string + */ + public function createManageLink($dept) + { + $linkHtml = $dept->name; + $linkHtml .= ' ' . html::a(helper::createLink('dept', 'browse', "deptid={$dept->id}"), $this->lang->dept->manageChild); + $linkHtml .= ' ' . html::a(helper::createLink('dept', 'delete', "deptid={$dept->id}"), $this->lang->dept->delete, 'hiddenwin'); + $linkHtml .= ' ' . html::input("orders[$dept->id]", $dept->order, 'style="width:30px;text-align:center"'); + return $linkHtml; + } + + /** + * Create the member link. + * + * @param int $dept + * @access public + * @return string + */ + public function createMemberLink($dept) + { + $linkHtml = html::a(helper::createLink('company', 'browse', "dept={$dept->id}"), $dept->name, '_self', "id='dept{$dept->id}'"); + return $linkHtml; + } + + /** + * Get sons of a department. + * + * @param int $deptID + * @access public + * @return array + */ + public function getSons($deptID) + { + return $this->dao->select('*')->from(TABLE_DEPT)->where('parent')->eq($deptID)->orderBy('`order`')->fetchAll(); + } + + /** + * Get all childs. + * + * @param int $deptID + * @access public + * @return array + */ + public function getAllChildId($deptID) + { + if($deptID == 0) return array(); + $dept = $this->getById($deptID); + $childs = $this->dao->select('id')->from(TABLE_DEPT)->where('path')->like($dept->path . '%')->fetchPairs(); + return array_keys($childs); + } + + /** + * Get parents. + * + * @param int $deptID + * @access public + * @return array + */ + public function getParents($deptID) + { + if($deptID == 0) return array(); + $path = $this->dao->select('path')->from(TABLE_DEPT)->where('id')->eq($deptID)->fetch('path'); + $path = substr($path, 1, -1); + if(empty($path)) return array(); + return $this->dao->select('*')->from(TABLE_DEPT)->where('id')->in($path)->orderBy('grade')->fetchAll(); + } + + /** + * Update order. + * + * @param int $orders + * @access public + * @return void + */ + public function updateOrder($orders) + { + foreach($orders as $deptID => $order) $this->dao->update(TABLE_DEPT)->set('`order`')->eq($order)->where('id')->eq($deptID)->exec(); + } + + /** + * Manage childs. + * + * @param int $parentDeptID + * @param string $childs + * @access public + * @return void + */ + public function manageChild($parentDeptID, $childs) + { + $parentDept = $this->getByID($parentDeptID); + if($parentDept) + { + $grade = $parentDept->grade + 1; + $parentPath = $parentDept->path; + } + else + { + $grade = 1; + $parentPath = ','; + } + + foreach($childs as $deptID => $deptName) + { + if(empty($deptName)) continue; + if(is_numeric($deptID)) + { + $dept->name = strip_tags($deptName); + $dept->parent = $parentDeptID; + $dept->grade = $grade; + $this->dao->insert(TABLE_DEPT)->data($dept)->exec(); + $deptID = $this->dao->lastInsertID(); + $childPath = $parentPath . "$deptID,"; + $this->dao->update(TABLE_DEPT)->set('path')->eq($childPath)->where('id')->eq($deptID)->exec(); + } + else + { + $deptID = str_replace('id', '', $deptID); + $this->dao->update(TABLE_DEPT)->set('name')->eq(strip_tags($deptName))->where('id')->eq($deptID)->exec(); + } + } + } + + /** + * Get users of a deparment. + * + * @param int $deptID + * @access public + * @return array + */ + public function getUsers($deptID) + { + return $this->dao->select('*')->from(TABLE_USER) + ->where('deleted')->eq(0) + ->beginIF($deptID)->andWhere('dept')->in($deptID)->fi() + ->orderBy('id') + ->fetchAll(); + } + + /** + * Delete a department. + * + * @param int $deptID + * @access public + * @return void + */ + public function delete($deptID) + { + $this->dao->delete()->from(TABLE_DEPT)->where('id')->eq($deptID)->exec(); + } +} diff --git a/trunk/module/dept/view/browse.html.php b/trunk/module/dept/view/browse.html.php new file mode 100644 index 0000000000..ad08d47328 --- /dev/null +++ b/trunk/module/dept/view/browse.html.php @@ -0,0 +1,67 @@ + + * @package dept + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
      +
      '> + + + + + +
      title;?>
      +
      +
      dept->updateOrder);?>
      +
      +
      +
      +
      '> + + + + + + + + + +
      dept->manageChild;?>
      + + createLink('dept', 'browse'), $this->app->company->name); + echo $lang->arrow; + foreach($parentDepts as $dept) + { + echo html::a($this->createLink('dept', 'browse', "deptID=$dept->id"), $dept->name); + echo $lang->arrow; + } + ?> + + + id]", $sonDept->name) . '
      '; + for($i = 0; $i < DEPT::NEW_CHILD_COUNT ; $i ++) echo html::input("depts[]") . '
      '; + ?> +
      + + +
      +
      +
      + diff --git a/trunk/module/doc/config.php b/trunk/module/doc/config.php new file mode 100644 index 0000000000..3db4a74af2 --- /dev/null +++ b/trunk/module/doc/config.php @@ -0,0 +1,41 @@ +doc->createLib->requiredFields = 'name'; +$config->doc->editLib->requiredFields = 'name'; +$config->doc->create->requiredFields = 'title'; +$config->doc->edit->requiredFields = 'title'; + +$config->doc->editor->create = array('id' => 'content', 'tools' => 'fullTools'); +$config->doc->editor->edit = array('id' => 'content', 'tools' => 'fullTools'); + +$config->doc->search['module'] = 'doc'; +$config->doc->search['fields']['title'] = $lang->doc->title; +$config->doc->search['fields']['id'] = $lang->doc->id; +$config->doc->search['fields']['keywords'] = $lang->doc->keywords; +$config->doc->search['fields']['product'] = $lang->doc->product; +$config->doc->search['fields']['module'] = $lang->doc->module; +$config->doc->search['fields']['project'] = $lang->doc->project; +$config->doc->search['fields']['type'] = $lang->doc->type; +$config->doc->search['fields']['lib'] = $lang->doc->lib; +$config->doc->search['fields']['digest'] = $lang->doc->digest; +$config->doc->search['fields']['content'] = $lang->doc->content; +$config->doc->search['fields']['url'] = $lang->doc->url; +$config->doc->search['fields']['addedBy'] = $lang->doc->addedBy; +$config->doc->search['fields']['addedDate'] = $lang->doc->addedDate; +$config->doc->search['fields']['editedBy'] = $lang->doc->editedBy; +$config->doc->search['fields']['editedDate'] = $lang->doc->editedDate; + +$config->doc->search['params']['title'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->doc->search['params']['keywords'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->doc->search['params']['product'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->doc->search['params']['module'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->doc->search['params']['project'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->doc->search['params']['type'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->doc->types); +$config->doc->search['params']['lib'] = array('operator' => '=', 'control' => 'select', 'values' => '' ); +$config->doc->search['params']['digest'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->doc->search['params']['content'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->doc->search['params']['url'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->doc->search['params']['addedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->doc->search['params']['addedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => ''); +$config->doc->search['params']['editedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->doc->search['params']['editedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => ''); diff --git a/trunk/module/doc/control.php b/trunk/module/doc/control.php new file mode 100644 index 0000000000..23f3fe5250 --- /dev/null +++ b/trunk/module/doc/control.php @@ -0,0 +1,422 @@ + + * @package doc + * @version $Id: control.php 933 2010-07-06 06:53:40Z wwccss $ + * @link http://www.zentao.net + */ +class doc extends control +{ + /** + * Construct function, load user, tree, action auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('user'); + $this->loadModel('tree'); + $this->loadModel('action'); + $this->loadModel('product'); + $this->loadModel('project'); + $this->libs = $this->doc->getLibs(); + } + + /** + * Go to browse page. + * + * @access public + * @return void + */ + public function index() + { + $this->locate(inlink('browse')); + } + + /** + * Browse docs. + * + * @param string|int $libID product|project or the int id of custom library + * @param int $moduleID + * @param int $productID + * @param int $projectID + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browse($libID = 'product', $moduleID = 0, $productID = 0, $projectID = 0, $browseType = 'byModule', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Set browseType.*/ + $browseType = strtolower($browseType); + $queryID = ($browseType == 'bysearch') ? (int)$param : 0; + + /* Set menu, save session. */ + $this->doc->setMenu($this->libs, $libID, 'doc'); + $this->session->set('docList', $this->app->getURI(true)); + + /* Set header and position. */ + $this->view->header->title = $this->lang->doc->index . $this->lang->colon . $this->libs[$libID]; + $this->view->position[] = $this->libs[$libID]; + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + /* Get docs. */ + $modules = 0; + $docs=array(); + if($browseType == "bymodule") + { + if($moduleID) $modules = $this->tree->getAllChildID($moduleID); + $docs = $this->doc->getDocs($libID, $productID, $projectID, $modules, $orderBy, $pager); + } + elseif($browseType == "bysearch") + { + if($queryID) + { + $query = $this->loadModel('search')->getQuery($queryID); + if($query) + { + $this->session->set('docQuery', $query->sql); + $this->session->set('docForm', $query->form); + } + else + { + $this->session->set('docQuery', ' 1 = 1'); + } + } + else + { + if($this->session->docQuery == false) $this->session->set('docQuery', ' 1 = 1'); + } + $docQuery = str_replace("`product` = 'all'", '1', $this->session->docQuery); // Search all producti. + $docQuery = str_replace("`project` = 'all'", '1', $docQuery); // Search all project. + $docs = $this->dao->select('*')->from(TABLE_DOC)->where($docQuery) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + + /* Get the tree menu. */ + if($libID == 'product') + { + $moduleTree = $this->tree->getProductDocTreeMenu(); + } + elseif($libID == 'project') + { + $moduleTree = $this->tree->getProjectDocTreeMenu(); + } + else + { + $moduleTree = $this->tree->getTreeMenu($libID, $viewType = 'customdoc', $startModuleID = 0, array('treeModel', 'createDocLink')); + } + + /* Build the search form. */ + $this->config->doc->search['actionURL'] = $this->createLink('doc', 'browse', "libID=$libID&moduleID=$moduleID&procuctID=$productID&projectID=$projectID&browseType=bySearch&queryID=myQueryID"); + $this->config->doc->search['queryID'] = $queryID; + $this->config->doc->search['params']['product']['values'] = array(''=>'') + $this->product->getPairs() + array('all'=>$this->lang->doc->allProduct); + $this->config->doc->search['params']['project']['values'] = array(''=>'') + $this->project->getPairs() + array('all'=>$this->lang->doc->allProject); + $this->config->doc->search['params']['lib']['values'] = array(''=>'') + $this->libs; + $this->config->doc->search['params']['type']['values'] = array(''=>'') + $this->config->doc->search['params']['type']['values']; + + /* Get the modules. */ + if($libID == 'product' or $libID == 'project') + { + $moduleOptionMenu = $this->tree->getOptionMenu(0, $libID . 'doc', $startModuleID = 0); + } + else + { + $moduleOptionMenu = $this->tree->getOptionMenu($libID, 'customdoc', $startModuleID = 0); + } + $this->config->doc->search['params']['module']['values'] = array(''=>'') + $moduleOptionMenu; + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->doc->search); + + $this->view->libID = $libID; + $this->view->libName = $this->libs[$libID]; + $this->view->moduleID = $moduleID; + $this->view->moduleTree = $moduleTree; + $this->view->parentModules = $this->tree->getParents($moduleID); + $this->view->docs = $docs; + $this->view->pager = $pager; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->orderBy = $orderBy; + $this->view->productID = $productID; + $this->view->projectID = $projectID; + $this->view->browseType = $browseType; + $this->view->param = $param; + + $this->display(); + } + + /** + * Create a library. + * + * @access public + * @return void + */ + public function createLib() + { + if(!empty($_POST)) + { + $libID = $this->doc->createLib(); + if(!dao::isError()) + { + $this->loadModel('action')->create('docLib', $libID, 'Created'); + die(js::locate($this->createLink($this->moduleName, 'browse', "libID=$libID"), 'parent')); + } + else + { + echo js::error(dao::getError()); + } + } + die($this->display()); + } + + /** + * Edit a library. + * + * @param int $libID + * @access public + * @return void + */ + public function editLib($libID) + { + if(!empty($_POST)) + { + $changes = $this->doc->updateLib($libID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('docLib', $libID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate($this->createLink($this->moduleName, 'browse', "libID=$libID"), 'parent')); + } + + $lib = $this->doc->getLibByID($libID); + $this->view->libName = empty($lib) ? $libID : $lib->name; + $this->view->libID = $libID; + + die($this->display()); + } + + /** + * Delete a library. + * + * @param int $libID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function deleteLib($libID, $confirm = 'no') + { + if($libID == 'product' or $libID == 'project') die(); + if($confirm == 'no') + { + die(js::confirm($this->lang->doc->confirmDeleteLib, $this->createLink('doc', 'deleteLib', "libID=$libID&confirm=yes"))); + } + else + { + $this->doc->delete(TABLE_DOCLIB, $libID); + die(js::locate($this->createLink('doc', 'browse'), 'parent')); + } + } + + /** + * Create a doc. + * + * @param int|string $libID + * @param int $moduleID + * @param int $productID + * @param int $projectID + * @param string $from + * @access public + * @return void + */ + public function create($libID, $moduleID = 0, $productID = 0, $projectID = 0, $from = 'doc') + { + $projectID = (int)$projectID; + if(!empty($_POST)) + { + $docID = $this->doc->create(); + if(dao::isError()) die(js::error(dao::getError())); + $this->action->create('doc', $docID, 'Created'); + + if($from == 'product') $link = $this->createLink('product', 'doc', "productID={$this->post->product}"); + if($from == 'project') $link = $this->createLink('project', 'doc', "projectID={$this->post->project}"); + if($from == 'doc') + { + $productID = intval($this->post->product); + $projectID = intval($this->post->project); + $vars = "libID=$libID&moduleID={$this->post->module}&productID=$productID&projectID=$projectID"; + $link = $this->createLink('doc', 'browse', $vars); + } + die(js::locate($link, 'parent')); + } + + $this->loadModel('product'); + $this->loadModel('project'); + + /* According the from, set menus. */ + if($from == 'product') + { + $this->lang->doc->menu = $this->lang->product->menu; + $this->lang->doc->menuOrder = $this->lang->product->menuOrder; + $this->product->setMenu($this->product->getPairs(), $productID); + $this->lang->set('menugroup.doc', 'product'); + } + elseif($from == 'project') + { + $this->lang->doc->menu = $this->lang->project->menu; + $this->lang->doc->menuOrder = $this->lang->project->menuOrder; + $this->project->setMenu($this->project->getPairs('nocode'), $projectID); + $this->lang->set('menugroup.doc', 'project'); + } + else + { + $this->doc->setMenu($this->libs, $libID); + } + + /* Get the modules. */ + if($libID == 'product' or $libID == 'project') + { + $moduleOptionMenu = $this->tree->getOptionMenu(0, $libID . 'doc', $startModuleID = 0); + } + else + { + $moduleOptionMenu = $this->tree->getOptionMenu($libID, 'customdoc', $startModuleID = 0); + } + + $this->view->header->title = $this->libs[$libID] . $this->lang->colon . $this->lang->doc->create; + $this->view->position[] = html::a($this->createLink('doc', 'browse', "libID=$libID"), $this->libs[$libID]); + $this->view->position[] = $this->lang->doc->create; + + $this->view->libID = $libID; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->moduleID = $moduleID; + $this->view->productID = $productID; + $this->view->projectID = $projectID; + $this->view->products = $projectID == 0 ? $this->product->getPairs() : $this->project->getProducts($projectID); + $this->view->projects = $this->loadModel('project')->getPairs('all'); + + $this->display(); + } + + /** + * Edit a doc. + * + * @param int $docID + * @access public + * @return void + */ + public function edit($docID) + { + if(!empty($_POST)) + { + $changes = $this->doc->update($docID); + if(dao::isError()) die(js::error(dao::getError())); + $files = $this->loadModel('file')->saveUpload('doc', $docID); + if($this->post->comment != '' or !empty($changes) or !empty($files)) + { + $action = !empty($changes) ? 'Edited' : 'Commented'; + $fileAction = ''; + if(!empty($files)) $fileAction = $this->lang->addFiles . join(',', $files) . "\n" ; + $actionID = $this->action->create('doc', $docID, $action, $fileAction . $this->post->comment); + $this->action->logHistory($actionID, $changes); + } + die(js::locate($this->createLink('doc', 'view', "docID=$docID"), 'parent')); + } + + /* Get doc and set menu. */ + $doc = $this->doc->getById($docID); + $libID = $doc->lib; + $this->doc->setMenu($this->libs, $libID); + + /* Get modules. */ + if($libID == 'product' or $libID == 'project') + { + $moduleOptionMenu = $this->tree->getOptionMenu(0, $libID . 'doc', $startModuleID = 0); + } + else + { + $moduleOptionMenu = $this->tree->getOptionMenu($libID, 'customdoc', $startModuleID = 0); + } + + $this->view->header->title = $this->libs[$libID] . $this->lang->colon . $this->lang->doc->create; + $this->view->position[] = html::a($this->createLink('doc', 'browse', "libID=$libID"), $this->libs[$libID]); + $this->view->position[] = $this->lang->doc->edit; + + $this->view->doc = $doc; + $this->view->libID = $libID; + $this->view->users = $this->user->getPairs('noclosed,nodeleted'); + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->display(); + } + + /** + * View a doc. + * + * @param int $docID + * @access public + * @return void + */ + public function view($docID) + { + /* Get doc. */ + $doc = $this->doc->getById($docID, true); + if(!$doc) die(js::error($this->lang->notFound) . js::locate('back')); + if($doc->project != 0 and !$this->project->checkPriv($this->project->getById($doc->project))) + { + echo(js::alert($this->lang->error->accessDenied)); + die(js::locate('back')); + } + + /* Get library. */ + $lib = $doc->libName; + if($doc->lib == 'product') $lib = $doc->productName; + if($doc->lib == 'project') $lib = $doc->productName . $this->lang->arrow . $doc->projectName; + + /* Set menu. */ + $this->doc->setMenu($this->libs, $doc->lib); + + $this->view->header->title = "DOC #$doc->id $doc->title - " . $this->libs[$doc->lib]; + $this->view->position[] = html::a($this->createLink('doc', 'browse', "libID=$doc->lib"), $this->libs[$doc->lib]); + $this->view->position[] = $this->lang->doc->view; + + $this->view->doc = $doc; + $this->view->lib = $lib; + $this->view->actions = $this->loadModel('action')->getList('doc', $docID); + $this->view->users = $this->user->getPairs('noclosed,nodeleted'); + + $this->display(); + } + + /** + * Delete a doc. + * + * @param int $docID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($docID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->doc->confirmDelete, inlink('delete', "docID=$docID&confirm=yes"))); + } + else + { + $this->doc->delete(TABLE_DOC, $docID); + die(js::locate($this->session->docList, 'parent')); + } + } +} diff --git a/trunk/module/doc/css/common.css b/trunk/module/doc/css/common.css new file mode 100644 index 0000000000..3861510a69 --- /dev/null +++ b/trunk/module/doc/css/common.css @@ -0,0 +1 @@ +.outer{overflow:auto} diff --git a/trunk/module/doc/css/createlib.css b/trunk/module/doc/css/createlib.css new file mode 100644 index 0000000000..8ab73a0b3b --- /dev/null +++ b/trunk/module/doc/css/createlib.css @@ -0,0 +1 @@ +body{background:white; margin-top:20px; padding-bottom:0} diff --git a/trunk/module/doc/css/editlib.css b/trunk/module/doc/css/editlib.css new file mode 100644 index 0000000000..8ab73a0b3b --- /dev/null +++ b/trunk/module/doc/css/editlib.css @@ -0,0 +1 @@ +body{background:white; margin-top:20px; padding-bottom:0} diff --git a/trunk/module/doc/js/browse.js b/trunk/module/doc/js/browse.js new file mode 100644 index 0000000000..5af6baa1ac --- /dev/null +++ b/trunk/module/doc/js/browse.js @@ -0,0 +1,15 @@ +/* Browse by module. */ +function browseByModule() +{ + $('#treebox').removeClass('hidden'); + $('.divider').removeClass('hidden'); + $('#bymoduleTab').addClass('active'); + $('#allTab').removeClass('active'); + $('#bysearchTab').removeClass('active'); + $('#querybox').addClass('hidden'); +} + +$(function(){ + $('#' + browseType + 'Tab').addClass('active'); + if(browseType == "bysearch")search(); +}); diff --git a/trunk/module/doc/js/common.js b/trunk/module/doc/js/common.js new file mode 100644 index 0000000000..4e449d5799 --- /dev/null +++ b/trunk/module/doc/js/common.js @@ -0,0 +1,29 @@ +/* Load the products of the roject. */ +function loadProducts(project) +{ + link = createLink('project', 'ajaxGetProducts', 'projectID=' + project); + $('#productBox').load(link); +} + +/* Set doc type. */ +function setType(type) +{ + if(type == 'url') + { + $('#urlBox').show(); + $('#fileBox').hide(); + $('#contentBox').hide(); + } + else if(type == 'text') + { + $('#urlBox').hide(); + $('#fileBox').hide(); + $('#contentBox').show(); + } + else + { + $('#urlBox').hide(); + $('#fileBox').show(); + $('#contentBox').hide(); + } +} diff --git a/trunk/module/doc/js/create.js b/trunk/module/doc/js/create.js new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/trunk/module/doc/js/create.js @@ -0,0 +1 @@ + diff --git a/trunk/module/doc/lang/en.php b/trunk/module/doc/lang/en.php new file mode 100644 index 0000000000..7cf7afcc17 --- /dev/null +++ b/trunk/module/doc/lang/en.php @@ -0,0 +1,69 @@ + + * @package doc + * @version $Id: en.php 824 2010-05-02 15:32:06Z wwccss $ + * @link http://www.zentao.net + */ +/* Fields. */ +$lang->doc->common = 'Doc'; +$lang->doc->id = 'ID'; +$lang->doc->product = 'Product'; +$lang->doc->project = 'Project'; +$lang->doc->lib = 'Library'; +$lang->doc->module = 'Module'; +$lang->doc->title = 'Title'; +$lang->doc->digest = 'Digest'; +$lang->doc->comment = 'Comment'; +$lang->doc->type = 'Type'; +$lang->doc->content = 'Content'; +$lang->doc->keywords = 'Keywords'; +$lang->doc->url = 'URL'; +$lang->doc->files = 'File'; +$lang->doc->views = 'Views'; +$lang->doc->addedBy = 'Added by'; +$lang->doc->addedDate = 'Added date'; +$lang->doc->editedBy = 'Edited by'; +$lang->doc->editedDate = 'Edited date'; +$lang->doc->manage = 'Doc type'; +$lang->doc->manageType = 'Manage type'; + +$lang->doc->moduleDoc = 'By module'; +$lang->doc->searchDoc = 'By search'; +//$lang->doc->allDoc = 'All document'; + +$lang->doc->moduleName = 'Module name'; +$lang->doc->moduleOrder = 'Module order'; + +/* Actions. */ +$lang->doc->index = 'Index'; +$lang->doc->create = 'Create doc'; +$lang->doc->edit = 'Edit doc'; +$lang->doc->delete = 'Delete doc'; +$lang->doc->browse = 'Browse doc'; +$lang->doc->view = 'View doc'; +$lang->doc->createLib = 'Create library'; +$lang->doc->editLib = 'Edit library'; +$lang->doc->deleteLib = 'Delete library'; +$lang->doc->libName = 'Library name'; + +/* Browse tabs. */ +$lang->doc->allProduct = 'All products'; +$lang->doc->allProject = 'All projects'; + +$lang->doc->systemLibs['product'] = 'Product doc'; +$lang->doc->systemLibs['project'] = 'Project doc'; + +$lang->doc->types['file'] = 'File'; +$lang->doc->types['url'] = 'Link'; +$lang->doc->types['text'] = 'Html'; + +$lang->doc->confirmDelete = "Are you sure to delete this doc?"; +$lang->doc->confirmDeleteLib = " Are you sure to delete this doc library?"; +$lang->doc->errorEditSystemDoc = "System doc library needn't edit"; + +$lang->doc->placeholder->url = 'url'; diff --git a/trunk/module/doc/lang/zh-cn.php b/trunk/module/doc/lang/zh-cn.php new file mode 100644 index 0000000000..913f94ba6f --- /dev/null +++ b/trunk/module/doc/lang/zh-cn.php @@ -0,0 +1,69 @@ + + * @package doc + * @version $Id: zh-cn.php 824 2010-05-02 15:32:06Z wwccss $ + * @link http://www.zentao.net + */ +/* 字段列表。*/ +$lang->doc->common = '文档视图'; +$lang->doc->id = '文档编号'; +$lang->doc->product = '所属产品'; +$lang->doc->project = '所属项目'; +$lang->doc->lib = '所属文档库'; +$lang->doc->module = '所属分类'; +$lang->doc->title = '文档标题'; +$lang->doc->digest = '文档摘要'; +$lang->doc->comment = '文档备注'; +$lang->doc->type = '文档类型'; +$lang->doc->content = '文档正文'; +$lang->doc->keywords = '关键字'; +$lang->doc->url = '文档URL'; +$lang->doc->files = '附件'; +$lang->doc->views = '查阅次数'; +$lang->doc->addedBy = '由谁添加'; +$lang->doc->addedDate = '添加时间'; +$lang->doc->editedBy = '由谁编辑'; +$lang->doc->editedDate = '编辑时间'; +$lang->doc->manage = '文档分类'; +$lang->doc->manageType = '维护分类'; + +$lang->doc->moduleDoc = '按模块浏览'; +$lang->doc->searchDoc = '搜索'; +//$lang->doc->allDoc = '所有文档'; + +$lang->doc->moduleName = '模块名称'; +$lang->doc->moduleOrder = '模块排序'; + +/* 方法列表。*/ +$lang->doc->index = '首页'; +$lang->doc->create = '创建文档'; +$lang->doc->edit = '编辑文档'; +$lang->doc->delete = '删除文档'; +$lang->doc->browse = '文档列表'; +$lang->doc->view = '文档详情'; +$lang->doc->createLib = '创建文档库'; +$lang->doc->editLib = '编辑文档库'; +$lang->doc->deleteLib = '删除文档库'; +$lang->doc->libName = '文档库名称'; + +/* 查询条件列表 */ +$lang->doc->allProduct = '所有产品'; +$lang->doc->allProject = '所有项目'; + +$lang->doc->systemLibs['product'] = '产品文档库'; +$lang->doc->systemLibs['project'] = '项目文档库'; + +$lang->doc->types['file'] = '文件'; +$lang->doc->types['url'] = '链接'; +$lang->doc->types['text'] = '网页'; + +$lang->doc->confirmDelete = "您确定删除该文档吗?"; +$lang->doc->confirmDeleteLib = " 您确定删除该文档库吗?"; +$lang->doc->errorEditSystemDoc = "系统文档库无需修改。"; + +$lang->doc->placeholder->url = '相应的链接地址'; diff --git a/trunk/module/doc/lang/zh-tw.php b/trunk/module/doc/lang/zh-tw.php new file mode 100644 index 0000000000..b60c939499 --- /dev/null +++ b/trunk/module/doc/lang/zh-tw.php @@ -0,0 +1,69 @@ + + * @package doc + * @version $Id: zh-tw.php 824 2010-05-02 15:32:06Z wwccss $ + * @link http://www.zentao.net + */ +/* 欄位列表。*/ +$lang->doc->common = '文檔視圖'; +$lang->doc->id = '文檔編號'; +$lang->doc->product = '所屬產品'; +$lang->doc->project = '所屬項目'; +$lang->doc->lib = '所屬文檔庫'; +$lang->doc->module = '所屬分類'; +$lang->doc->title = '文檔標題'; +$lang->doc->digest = '文檔摘要'; +$lang->doc->comment = '文檔備註'; +$lang->doc->type = '文檔類型'; +$lang->doc->content = '文檔正文'; +$lang->doc->keywords = '關鍵字'; +$lang->doc->url = '文檔URL'; +$lang->doc->files = '附件'; +$lang->doc->views = '查閲次數'; +$lang->doc->addedBy = '由誰添加'; +$lang->doc->addedDate = '添加時間'; +$lang->doc->editedBy = '由誰編輯'; +$lang->doc->editedDate = '編輯時間'; +$lang->doc->manage = '文檔分類'; +$lang->doc->manageType = '維護分類'; + +$lang->doc->moduleDoc = '按模組瀏覽'; +$lang->doc->searchDoc = '搜索'; +//$lang->doc->allDoc = '所有文檔'; + +$lang->doc->moduleName = '模組名稱'; +$lang->doc->moduleOrder = '模組排序'; + +/* 方法列表。*/ +$lang->doc->index = '首頁'; +$lang->doc->create = '創建文檔'; +$lang->doc->edit = '編輯文檔'; +$lang->doc->delete = '刪除文檔'; +$lang->doc->browse = '文檔列表'; +$lang->doc->view = '文檔詳情'; +$lang->doc->createLib = '創建文檔庫'; +$lang->doc->editLib = '編輯文檔庫'; +$lang->doc->deleteLib = '刪除文檔庫'; +$lang->doc->libName = '文檔庫名稱'; + +/* 查詢條件列表 */ +$lang->doc->allProduct = '所有產品'; +$lang->doc->allProject = '所有項目'; + +$lang->doc->systemLibs['product'] = '產品文檔庫'; +$lang->doc->systemLibs['project'] = '項目文檔庫'; + +$lang->doc->types['file'] = '檔案'; +$lang->doc->types['url'] = '連結'; +$lang->doc->types['text'] = '網頁'; + +$lang->doc->confirmDelete = "您確定刪除該文檔嗎?"; +$lang->doc->confirmDeleteLib = " 您確定刪除該文檔庫嗎?"; +$lang->doc->errorEditSystemDoc = "系統文檔庫無需修改。"; + +$lang->doc->placeholder->url = '相應的連結地址'; diff --git a/trunk/module/doc/model.php b/trunk/module/doc/model.php new file mode 100644 index 0000000000..780b43a2dd --- /dev/null +++ b/trunk/module/doc/model.php @@ -0,0 +1,282 @@ + + * @package doc + * @version $Id: model.php 881 2010-06-22 06:50:32Z chencongzhi520 $ + * @link http://www.zentao.net + */ +?> +app->getModuleName(); + $currentMethod = $this->app->getMethodName(); + + $selectHtml = html::select('libID', $libs, $libID, "onchange=\"switchDocLib(this.value, '$currentModule', '$currentMethod', '$extra');\""); + common::setMenuVars($this->lang->doc->menu, 'list', $selectHtml . $this->lang->arrow); + foreach($this->lang->doc->menu as $key => $menu) + { + if($key != 'list') common::setMenuVars($this->lang->doc->menu, $key, $libID); + } + } + + /** + * Get library by id. + * + * @param int $libID + * @access public + * @return object + */ + public function getLibById($libID) + { + return $this->dao->findByID($libID)->from(TABLE_DOCLIB)->fetch(); + } + + /** + * Get libraries. + * + * @access public + * @return array + */ + public function getLibs() + { + $libs = $this->dao->select('id, name')->from(TABLE_DOCLIB)->where('deleted')->eq(0)->fetchPairs(); + return $this->lang->doc->systemLibs + $libs; + } + + /** + * Create a library. + * + * @access public + * @return void + */ + public function createLib() + { + $lib = fixer::input('post')->stripTags('name')->get(); + $this->dao->insert(TABLE_DOCLIB) + ->data($lib) + ->autoCheck() + ->batchCheck('name', 'notempty') + ->check('name', 'unique') + ->exec(); + return $this->dao->lastInsertID(); + } + + /** + * Update a library. + * + * @param int $libID + * @access public + * @return void + */ + public function updateLib($libID) + { + $libID = (int)$libID; + $oldLib = $this->getLibById($libID); + $lib = fixer::input('post')->stripTags('name')->get(); + $this->dao->update(TABLE_DOCLIB) + ->data($lib) + ->autoCheck() + ->batchCheck('name', 'notempty') + ->check('name', 'unique', "id != $libID") + ->where('id')->eq($libID) + ->exec(); + if(!dao::isError()) return common::createChanges($oldLib, $lib); + } + + /** + * Get docs. + * + * @param int|string $libID + * @param int $productID + * @param int $projectID + * @param int $module + * @param string $orderBy + * @param object $pager + * @access public + * @return void + */ + public function getDocs($libID, $productID, $projectID, $module, $orderBy, $pager) + { + $products = $this->loadModel('product')->getPairs(); + $projects = $this->loadModel('project')->getPairs(); + $keysOfProducts = array_keys($products); + $keysOfProjects = array_keys($projects); + $allKeysOfProjects = $keysOfProjects; + $allKeysOfProjects[] = 0; + + return $this->dao->select('*')->from(TABLE_DOC) + ->where('deleted')->eq(0) + ->beginIF(is_numeric($libID))->andWhere('lib')->eq($libID)->fi() + ->beginIF($libID == 'product')->andWhere('product')->in($keysOfProducts)->andWhere('project')->in($allKeysOfProjects)->fi() + ->beginIF($libID == 'project')->andWhere('project')->in($keysOfProjects)->fi() + ->beginIF($productID > 0)->andWhere('product')->eq($productID)->fi() + ->beginIF($projectID > 0)->andWhere('project')->eq($projectID)->fi() + ->beginIF((string)$projectID == 'int')->andWhere('project')->gt(0)->fi() + ->beginIF($module)->andWhere('module')->in($module)->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + } + + /** + * Get doc info by id. + * + * @param int $docID + * @param bool $setImgSize + * @access public + * @return void + */ + public function getById($docID, $setImgSize = false) + { + $doc = $this->dao->select('*') + ->from(TABLE_DOC) + ->where('id')->eq((int)$docID) + ->fetch(); + if(!$doc) return false; + if($setImgSize) $doc->content = $this->loadModel('file')->setImgSize($doc->content); + $doc->files = $this->loadModel('file')->getByObject('doc', $docID); + + $doc->libName = ''; + $doc->productName = ''; + $doc->projectName = ''; + $doc->moduleName = ''; + if($doc->lib) $doc->libName = $this->dao->findByID($doc->lib)->from(TABLE_DOCLIB)->fetch('name'); + if($doc->product) $doc->productName = $this->dao->findByID($doc->product)->from(TABLE_PRODUCT)->fetch('name'); + if($doc->project) $doc->projectName = $this->dao->findByID($doc->project)->from(TABLE_PROJECT)->fetch('name'); + if($doc->module) $doc->moduleName = $this->dao->findByID($doc->module)->from(TABLE_MODULE)->fetch('name'); + return $doc; + } + + /** + * Create a doc. + * + * @access public + * @return void + */ + public function create() + { + $now = helper::now(); + $doc = fixer::input('post') + ->add('addedBy', $this->app->user->account) + ->add('addedDate', $now) + ->setDefault('product, project, module', 0) + ->specialChars('title, digest, keywords') + ->encodeURL('url') + ->cleanInt('product, project, module') + ->remove('files, labels') + ->get(); + $condition = "lib = '$doc->lib' AND module = $doc->module"; + $this->dao->insert(TABLE_DOC) + ->data($doc) + ->autoCheck() + ->batchCheck($this->config->doc->create->requiredFields, 'notempty') + ->check('title', 'unique', $condition) + ->exec(); + if(!dao::isError()) + { + $docID = $this->dao->lastInsertID(); + $this->loadModel('file')->saveUpload('doc', $docID); + return $docID; + } + return false; + } + + /** + * Update a doc. + * + * @param int $docID + * @access public + * @return void + */ + public function update($docID) + { + $oldDoc = $this->getById($docID); + $now = helper::now(); + $doc = fixer::input('post') + ->cleanInt('module') + ->setDefault('module', 0) + ->specialChars('title, digest, keywords') + ->encodeURL('url') + ->remove('comment,files, labels') + ->add('editedBy', $this->app->user->account) + ->add('editedDate', $now) + ->get(); + + $condition = "lib = '$doc->lib' AND module = $doc->module AND id != $docID"; + $this->dao->update(TABLE_DOC)->data($doc) + ->autoCheck() + ->batchCheck($this->config->doc->edit->requiredFields, 'notempty') + ->check('title', 'unique', $condition) + ->where('id')->eq((int)$docID) + ->exec(); + if(!dao::isError()) return common::createChanges($oldDoc, $doc); + } + + /** + * Get docs of a product. + * + * @param int $productID + * @access public + * @return array + */ + public function getProductDocs($productID) + { + return $this->dao->select('t1.*, t2.name as module') + ->from(TABLE_DOC)->alias('t1') + ->leftjoin(TABLE_MODULE)->alias('t2')->on('t1.module = t2.id') + ->where('t1.product')->eq($productID) + ->andWhere('t1.deleted')->eq(0) + ->orderBy('t1.id_desc') + ->fetchAll(); + } + + /** + * Get docs of a project. + * + * @param int $projectID + * @access public + * @return array + */ + public function getProjectDocs($projectID) + { + return $this->dao->findByProject($projectID)->from(TABLE_DOC)->andWhere('deleted')->eq(0)->orderBy('id_desc')->fetchAll(); + } + + /** + * Get pairs of product modules. + * + * @access public + * @return array + */ + public function getProductModulePairs() + { + return $this->dao->findByType('productdoc')->from(TABLE_MODULE)->fetchPairs('id', 'name'); + } + + /** + * Get pairs of project modules. + * + * @access public + * @return array + */ + public function getProjectModulePairs() + { + return $this->dao->findByType('projectdoc')->from(TABLE_MODULE)->andWhere('type')->eq('projectdoc')->fetchPairs('id', 'name'); + } +} diff --git a/trunk/module/doc/view/browse.html.php b/trunk/module/doc/view/browse.html.php new file mode 100644 index 0000000000..a732fb9ead --- /dev/null +++ b/trunk/module/doc/view/browse.html.php @@ -0,0 +1,84 @@ + + * @package lib + * @version $Id: browse.html.php 958 2010-07-22 08:09:42Z wwccss $ + * @link http://www.zentao.net + */ +?> + + + + + +
      + +
      + doc->create);?> +
      +
      +
      '>
      + + + + + + + +
      +
      +
      + +
      + doc->manage);?> + tree->fix, 'hiddenwin');?> +
      +
      +
      + + + + recTotal}&recPerPage={$pager->recPerPage}";?> + + + + + + + + + + $doc):?> + createLink('doc', 'view', "docID=$doc->id"); + $canView = common::hasPriv('doc', 'view'); + ?> + + + + + + + + + + + +
      idAB);?>doc->title);?>doc->type);?>doc->addedBy);?>doc->addedDate);?>actions;?>
      id)); else printf('%03d', $doc->id);?>title);?>doc->types[$doc->type];?>addedBy]) ? print($users[$doc->addedBy]) : print($doc->addedBy);?>addedDate));?> + id}"; + if(!common::printLink('doc', 'edit', $vars, $lang->edit)) echo $lang->edit; + if(!common::printLink('doc', 'delete', $vars, $lang->delete, 'hiddenwin')) echo $lang->delete; + ?> +
      show();?>
      +
      + diff --git a/trunk/module/doc/view/create.html.php b/trunk/module/doc/view/create.html.php new file mode 100644 index 0000000000..455a5473df --- /dev/null +++ b/trunk/module/doc/view/create.html.php @@ -0,0 +1,71 @@ + + * @package doc + * @version $Id: create.html.php 975 2010-07-29 03:30:25Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      doc->create;?>
      doc->product;?>
      doc->project;?>
      doc->product;?>
      doc->module;?>
      doc->type;?>doc->types, 'file', "onclick=setType(this.value)");?>
      doc->title;?>
      doc->keywords;?>
      doc->digest;?>
      doc->files;?>fetch('file', 'buildform', 'fileCount=2');?>
      +
      + diff --git a/trunk/module/doc/view/createlib.html.php b/trunk/module/doc/view/createlib.html.php new file mode 100644 index 0000000000..da2f773981 --- /dev/null +++ b/trunk/module/doc/view/createlib.html.php @@ -0,0 +1,26 @@ + + * @package doc + * @version $Id: createlib.html.php 975 2010-07-29 03:30:25Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + +
      doc->createLib;?>
      doc->libName;?>
      +
      + diff --git a/trunk/module/doc/view/edit.html.php b/trunk/module/doc/view/edit.html.php new file mode 100644 index 0000000000..65f2163793 --- /dev/null +++ b/trunk/module/doc/view/edit.html.php @@ -0,0 +1,68 @@ + + * @package doc + * @version $Id: edit.html.php 975 2010-07-29 03:30:25Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      doc->edit;?>
      doc->module;?>module, "class='select-3'");?>
      doc->type;?>doc->types[$doc->type];?>
      doc->title;?>title, "class='text-1'");?>
      doc->keywords;?>keywords, "class='text-1'");?>
      doc->digest;?>digest, "class='text-1' rows=3");?>
      doc->comment;?>
      + + product) . html::hidden('project', $doc->project);?> +
      +
      + diff --git a/trunk/module/doc/view/editlib.html.php b/trunk/module/doc/view/editlib.html.php new file mode 100644 index 0000000000..72840811b5 --- /dev/null +++ b/trunk/module/doc/view/editlib.html.php @@ -0,0 +1,28 @@ + + * @package doc + * @version $Id: editlib.html.php 975 2010-07-29 03:30:25Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + +

      doc->errorEditSystemDoc;?>

      + +
      + + + + + + + +
      doc->editLib;?>
      doc->libName;?>
      +
      + + diff --git a/trunk/module/doc/view/footer.html.php b/trunk/module/doc/view/footer.html.php new file mode 100644 index 0000000000..ec142b03ae --- /dev/null +++ b/trunk/module/doc/view/footer.html.php @@ -0,0 +1,9 @@ + + + diff --git a/trunk/module/doc/view/view.html.php b/trunk/module/doc/view/view.html.php new file mode 100644 index 0000000000..5279eb5dde --- /dev/null +++ b/trunk/module/doc/view/view.html.php @@ -0,0 +1,68 @@ + + * @package doc + * @version $Id: view.html.php 975 2010-07-29 03:30:25Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + type != 'url') echo "class='hidden'";?>> + + + + type != 'text') echo "class='hidden'";?>> + + + + + + + + type != 'file') echo "class='hidden'";?>> + + + +
      DOC #id . ' ' . $doc->title?>
      doc->title;?>deleted) echo "class='deleted'";?>>title;?>
      doc->lib;?>
      doc->module;?>moduleName;?>
      doc->type;?>doc->types[$doc->type];?>
      doc->title;?>title;?>
      doc->keywords;?>keywords;?>
      doc->url;?>url), '', '_blank');?>
      doc->content;?>content;?>
      doc->digest;?>digest);?>
      files;?>fetch('file', 'printFiles', array('files' => $doc->files, 'fieldset' => 'false'));?>
      +
      + session->docList ? $this->session->docList : inlink('browse'); + if(!$doc->deleted) + { + common::printLink('doc', 'edit', "docID=$doc->id", $lang->edit); + common::printLink('doc', 'delete', "docID=$doc->id", $lang->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      + + diff --git a/trunk/module/editor/config.php b/trunk/module/editor/config.php new file mode 100644 index 0000000000..59e9e26969 --- /dev/null +++ b/trunk/module/editor/config.php @@ -0,0 +1,18 @@ +editor->sort[] = 'model.php'; +$config->editor->sort[] = 'control.php'; +$config->editor->sort[] = 'view'; +$config->editor->sort[] = 'lang'; +$config->editor->sort[] = 'config.php'; +$config->editor->sort[] = 'js'; +$config->editor->sort[] = 'css'; +$config->editor->sort[] = 'ext'; + +$config->editor->extSort[] = 'model'; +$config->editor->extSort[] = 'control'; +$config->editor->extSort[] = 'view'; +$config->editor->extSort[] = 'lang'; +$config->editor->extSort[] = 'js'; +$config->editor->extSort[] = 'css'; +$config->editor->extSort[] = 'config'; + diff --git a/trunk/module/editor/control.php b/trunk/module/editor/control.php new file mode 100644 index 0000000000..0470e2ce7e --- /dev/null +++ b/trunk/module/editor/control.php @@ -0,0 +1,164 @@ + + * @package editor + * @version $Id$ + * @link http://www.zentao.net + */ +class editor extends control +{ + /** + * Show module files and edit them. + * + * @access public + * @return void + */ + public function index() + { + $this->view->moduleList = $this->editor->getModules(); + $this->display(); + } + + /** + * Show this module of files. + * + * @param string $moduleDir + * @access public + * @return void + */ + public function extend($moduleDir = '') + { + $moduleFiles = $this->editor->getModuleFiles($moduleDir); + $this->view->module = $moduleDir; + $this->view->tree = $this->editor->printTree($moduleFiles); + $this->display(); + } + + /** + * Edit extend. + * + * @param string $filePath + * @param string $action + * @param string $isExtends + * @access public + * @return void + */ + public function edit($filePath = '', $action = '', $isExtends = '') + { + $this->view->safeFilePath = $filePath; + $fileContent = ''; + if($filePath) + { + $filePath = helper::safe64Decode($filePath); + if($action == 'extendOther' and file_exists($filePath)) + { + $this->view->showContent = htmlspecialchars(file_get_contents($filePath)); + } + if($action == 'edit' or $action == 'override') + { + if(file_exists($filePath)) + { + $fileContent = file_get_contents($filePath); + if($action == 'override') + { + $fileContent = str_replace('../../', '../../../', $fileContent); + $fileContent = str_replace(array('\'./', '"./'), array('\'../../view/', '"../../view'), $fileContent); + } + } + else + { + $filePath = ''; + } + } + elseif($action == 'extendModel') + { + $fileContent = $this->editor->extendModel($filePath); + } + elseif($action == 'extendControl') + { + $okUrl = $this->editor->getExtendLink($filePath, 'extendControl', 'yes'); + $cancelUrl = $this->editor->getExtendLink($filePath, 'extendControl', 'no'); + if(!$isExtends) die(js::confirm($this->lang->editor->extendConfirm, $okUrl, $cancelUrl)); + $fileContent = $this->editor->extendControl($filePath, $isExtends); + } + elseif($action == 'newPage') + { + $fileContent = $this->editor->newControl($filePath); + } + elseif(strrpos(basename($filePath), '.php') !== false and empty($fileContent)) + { + $fileContent = "view->fileContent = $fileContent; + $this->view->filePath = $filePath; + $this->view->action = $action; + $this->display(); + } + + /** + * Set Page name. + * + * @param string $filePath + * @access public + * @return void + */ + public function newPage($filePath) + { + $filePath = helper::safe64Decode($filePath); + if($_POST) + { + $saveFilePath = $this->editor->getSavePath($filePath, 'newMethod'); + $extendLink = $this->editor->getExtendLink($saveFilePath, 'newPage'); + if(file_exists($saveFilePath) and !$this->post->override) die(js::confirm($this->lang->editor->repeatPage, $extendLink, '', 'parent')); + die(js::locate($extendLink, 'parent')); + } + $this->view->filePath = $filePath; + $this->display(); + } + + /** + * Save file to extension. + * + * @param string $filePath + * @access public + * @return void + */ + public function save($filePath = '', $action = '') + { + if($filePath and $_POST) + { + $filePath = helper::safe64Decode($filePath); + if($action != 'edit' and $action != 'newPage') $filePath = $this->editor->getSavePath($filePath, $action); + if($action != 'edit' and $action != 'newPage' and file_exists($filePath) and !$this->post->override) die(js::error($this->lang->editor->repeatFile)); + $this->editor->save($filePath); + echo js::reload('parent.parent.extendWin'); + die(js::locate(inlink('edit', "filePath=" . helper::safe64Encode($filePath) . "&action=edit"), 'parent')); + } + } + + /** + * Delete extension file. + * + * @param string $filePath + * @param string $confirm + * @access public + * @return void + */ + public function delete($filePath = '', $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->editor->deleteConfirm, inlink('delete', "filePath=$filePath&confirm=yes"))); + } + $filePath = helper::safe64Decode($filePath); + if(file_exists($filePath) and unlink($filePath)) die(js::reload('parent')); + die(js::alert($this->lang->editor->notDelete)); + + } +} + diff --git a/trunk/module/editor/css/extend.css b/trunk/module/editor/css/extend.css new file mode 100644 index 0000000000..592bf5002d --- /dev/null +++ b/trunk/module/editor/css/extend.css @@ -0,0 +1,3 @@ +#extendTree li {list-style-type:none;} +#extendTree .active {color:blue; font-weight:bold} + diff --git a/trunk/module/editor/js/edit.js b/trunk/module/editor/js/edit.js new file mode 100644 index 0000000000..9d232393df --- /dev/null +++ b/trunk/module/editor/js/edit.js @@ -0,0 +1,16 @@ +$(function() +{ + var windowHeight = $(window).height(); + var showHeight = Math.ceil(windowHeight *0.3); + if($('#showContent').attr('id') != undefined) + { + var fileHeight = windowHeight - showHeight - 150; + $('#showContent').height(showHeight); + $('#fileContent').height(fileHeight); + } + else + { + var fileHeight = windowHeight - 140; + $('#fileContent').height(fileHeight); + } +}) diff --git a/trunk/module/editor/js/index.js b/trunk/module/editor/js/index.js new file mode 100644 index 0000000000..d3abaaee77 --- /dev/null +++ b/trunk/module/editor/js/index.js @@ -0,0 +1,6 @@ +$(function() +{ + var showHeight = $(window).height() - $('#header').height() - $('#footer').height() - 20; + $('#editWin').height(showHeight); + $('#extendWin').height(showHeight); +}); diff --git a/trunk/module/editor/lang/en.php b/trunk/module/editor/lang/en.php new file mode 100644 index 0000000000..8fd44d5c69 --- /dev/null +++ b/trunk/module/editor/lang/en.php @@ -0,0 +1,79 @@ +editor->newMethod = 'New method'; +$lang->editor->extend = 'Extend'; +$lang->editor->newLang = 'New language'; +$lang->editor->newConfig = 'New config'; +$lang->editor->newHook = 'New hook'; +$lang->editor->newExtend = 'New extend'; +$lang->editor->newPage = 'New page'; +$lang->editor->override = 'override'; +$lang->editor->edit = 'Edit extend'; + +$lang->editor->moduleList = 'Module list'; +$lang->editor->filePath = "Extend:"; +$lang->editor->sourceFile = 'Source file:'; +$lang->editor->fileName = "File name:"; +$lang->editor->isOverride = "Override repeat file"; +$lang->editor->exampleHook = "(For example:***.html.hook.php)"; +$lang->editor->exampleJs = "(For example:***.js)"; +$lang->editor->exampleCss = "(For example:***.css)"; +$lang->editor->examplePHP = "(For example:***.php)"; +$lang->editor->pageName = "Page Name:"; + +$lang->editor->deleteConfirm = 'Are you sure?'; +$lang->editor->extendConfirm = 'Would you reuse the code.'; +$lang->editor->repeatFile = 'File name repeat'; +$lang->editor->repeatPage = 'This page has extisted. Is override?'; + +$lang->editor->notWritable = "Can not writable, maybe no power. please try chmod 777 -R "; +$lang->editor->notDelete = "Can not delete, please check power!"; +$lang->editor->emptyFileName = 'Please write file name'; + +$lang->editor->translate['config.php'] = 'Config file'; +$lang->editor->translate['config'] = 'Config extend'; +$lang->editor->translate['control.php'] = 'Control file'; +$lang->editor->translate['control'] = 'Control extend'; +$lang->editor->translate['model.php'] = 'Model file'; +$lang->editor->translate['model'] = 'Model extend'; +$lang->editor->translate['view'] = 'View file'; +$lang->editor->translate['lang'] = 'Language file'; +$lang->editor->translate['en'] = 'English'; +$lang->editor->translate['zh-cn'] = 'Chinese Simple'; +$lang->editor->translate['zh-tw'] = 'Chinese Traditional'; +$lang->editor->translate['js'] = 'Javascript'; +$lang->editor->translate['css'] = 'Css'; +$lang->editor->translate['ext'] = 'Extend file'; + +$lang->editor->modules['action'] = 'Logs'; +$lang->editor->modules['admin'] = 'Admin'; +$lang->editor->modules['api'] = 'API'; +$lang->editor->modules['bug'] = 'Bug'; +$lang->editor->modules['build'] = 'Build'; +$lang->editor->modules['common'] = 'Common module'; +$lang->editor->modules['company'] = 'Company'; +$lang->editor->modules['convert'] = 'Import'; +$lang->editor->modules['dept'] = 'Department'; +$lang->editor->modules['doc'] = 'Doc'; +$lang->editor->modules['extension'] = 'Extension'; +$lang->editor->modules['file'] = 'File'; +$lang->editor->modules['group'] = 'Group'; +$lang->editor->modules['index'] = 'Index'; +$lang->editor->modules['install'] = 'Install'; +$lang->editor->modules['misc'] = 'Misc'; +$lang->editor->modules['my'] = 'Dashboard'; +$lang->editor->modules['product'] = 'Product'; +$lang->editor->modules['productplan'] = 'Plan'; +$lang->editor->modules['project'] = 'Project'; +$lang->editor->modules['qa'] = 'Test'; +$lang->editor->modules['release'] = 'Release'; +$lang->editor->modules['report'] = 'Report'; +$lang->editor->modules['search'] = 'Search'; +$lang->editor->modules['story'] = 'Story'; +$lang->editor->modules['task'] = 'Task'; +$lang->editor->modules['testcase'] = 'Case'; +$lang->editor->modules['testtask'] = 'Test task'; +$lang->editor->modules['todo'] = 'TODO'; +$lang->editor->modules['tree'] = 'Module manage'; +$lang->editor->modules['upgrade'] = 'Upgrade'; +$lang->editor->modules['user'] = 'User'; + diff --git a/trunk/module/editor/lang/zh-cn.php b/trunk/module/editor/lang/zh-cn.php new file mode 100644 index 0000000000..8775178391 --- /dev/null +++ b/trunk/module/editor/lang/zh-cn.php @@ -0,0 +1,78 @@ +editor->newMethod = '新增方法'; +$lang->editor->extend = '扩展'; +$lang->editor->newLang = '新增语言'; +$lang->editor->newConfig = '新增配置'; +$lang->editor->newHook = '新增钩子'; +$lang->editor->newExtend = '新增扩展'; +$lang->editor->newPage = '新增页面'; +$lang->editor->override = '覆盖'; +$lang->editor->edit = '编辑扩展'; + +$lang->editor->moduleList = '模块列表'; +$lang->editor->filePath = "扩展:"; +$lang->editor->sourceFile = '源文件:'; +$lang->editor->fileName = "文件名:"; +$lang->editor->isOverride = "覆盖重复文件"; +$lang->editor->exampleHook = "(例如:***.html.hook.php)"; +$lang->editor->exampleJs = "(例如:***.js)"; +$lang->editor->exampleCss = "(例如:***.css)"; +$lang->editor->examplePHP = "(例如:***.php)"; +$lang->editor->pageName = "页面名称:"; + +$lang->editor->deleteConfirm = '是否要删除?'; +$lang->editor->extendConfirm = '是否要重用原来代码?'; +$lang->editor->repeatFile = '文件名重复'; +$lang->editor->repeatPage = '已经有此页面。是否覆盖?'; + +$lang->editor->notWritable = "无法写入,可能没有权限。请尝试执行 chmod 777 -R "; +$lang->editor->notDelete = '无法删除,请检查权限!'; +$lang->editor->emptyFileName = '请写入一个文件名!'; + +$lang->editor->translate['config.php'] = '配置文件'; +$lang->editor->translate['config'] = '配置扩展'; +$lang->editor->translate['control.php'] = '控制文件'; +$lang->editor->translate['control'] = '控制扩展'; +$lang->editor->translate['model.php'] = '模型文件'; +$lang->editor->translate['model'] = '模型扩展'; +$lang->editor->translate['view'] = '模板文件'; +$lang->editor->translate['lang'] = '语言文件'; +$lang->editor->translate['en'] = '英文'; +$lang->editor->translate['zh-cn'] = '中文简体'; +$lang->editor->translate['zh-tw'] = '中文繁体'; +$lang->editor->translate['js'] = 'JS脚本'; +$lang->editor->translate['css'] = 'CSS样式'; +$lang->editor->translate['ext'] = '扩展文件'; + +$lang->editor->modules['action'] = '系统日志'; +$lang->editor->modules['admin'] = '后台管理'; +$lang->editor->modules['api'] = 'API接口'; +$lang->editor->modules['bug'] = '缺陷管理'; +$lang->editor->modules['build'] = 'Build'; +$lang->editor->modules['common'] = '公有模块'; +$lang->editor->modules['company'] = '组织视图'; +$lang->editor->modules['convert'] = '从其他系统导入'; +$lang->editor->modules['dept'] = '部门结构'; +$lang->editor->modules['doc'] = '文档视图'; +$lang->editor->modules['extension'] = '插件管理'; +$lang->editor->modules['file'] = '附件'; +$lang->editor->modules['group'] = '权限分组'; +$lang->editor->modules['index'] = '首页'; +$lang->editor->modules['install'] = '安装'; +$lang->editor->modules['misc'] = '杂项'; +$lang->editor->modules['my'] = '我的地盘'; +$lang->editor->modules['product'] = '产品视图'; +$lang->editor->modules['productplan'] = '产品计划'; +$lang->editor->modules['project'] = '项目视图'; +$lang->editor->modules['qa'] = '测试视图'; +$lang->editor->modules['release'] = '发布'; +$lang->editor->modules['report'] = '报表'; +$lang->editor->modules['search'] = '搜索'; +$lang->editor->modules['story'] = '需求'; +$lang->editor->modules['task'] = '任务'; +$lang->editor->modules['testcase'] = '用例管理'; +$lang->editor->modules['testtask'] = '测试任务'; +$lang->editor->modules['todo'] = 'TODO'; +$lang->editor->modules['tree'] = '模块维护'; +$lang->editor->modules['upgrade'] = '升级'; +$lang->editor->modules['user'] = '用户'; diff --git a/trunk/module/editor/lang/zh-tw.php b/trunk/module/editor/lang/zh-tw.php new file mode 100644 index 0000000000..d6fcf742f4 --- /dev/null +++ b/trunk/module/editor/lang/zh-tw.php @@ -0,0 +1,78 @@ +editor->newMethod = '新增方法'; +$lang->editor->extend = '擴展'; +$lang->editor->newLang = '新增語言'; +$lang->editor->newConfig = '新增配置'; +$lang->editor->newHook = '新增鈎子'; +$lang->editor->newExtend = '新增擴展'; +$lang->editor->newPage = '新增頁面'; +$lang->editor->override = '覆蓋'; +$lang->editor->edit = '編輯擴展'; + +$lang->editor->moduleList = '模組列表'; +$lang->editor->filePath = "擴展:"; +$lang->editor->sourceFile = '源檔案:'; +$lang->editor->fileName = "檔案名:"; +$lang->editor->isOverride = "覆蓋重複檔案"; +$lang->editor->exampleHook = "(例如:***.html.hook.php)"; +$lang->editor->exampleJs = "(例如:***.js)"; +$lang->editor->exampleCss = "(例如:***.css)"; +$lang->editor->examplePHP = "(例如:***.php)"; +$lang->editor->pageName = "頁面名稱:"; + +$lang->editor->deleteConfirm = '是否要刪除?'; +$lang->editor->extendConfirm = '是否要重用原來代碼?'; +$lang->editor->repeatFile = '檔案名重複'; +$lang->editor->repeatPage = '已經有此頁面。是否覆蓋?'; + +$lang->editor->notWritable = "無法寫入,可能沒有權限。請嘗試執行 chmod 777 -R "; +$lang->editor->notDelete = '無法刪除,請檢查權限!'; +$lang->editor->emptyFileName = '請寫入一個檔案名!'; + +$lang->editor->translate['config.php'] = '配置檔案'; +$lang->editor->translate['config'] = '配置擴展'; +$lang->editor->translate['control.php'] = '控制檔案'; +$lang->editor->translate['control'] = '控制擴展'; +$lang->editor->translate['model.php'] = '模型檔案'; +$lang->editor->translate['model'] = '模型擴展'; +$lang->editor->translate['view'] = '模板檔案'; +$lang->editor->translate['lang'] = '語言檔案'; +$lang->editor->translate['en'] = '英文'; +$lang->editor->translate['zh-tw'] = '中文簡體'; +$lang->editor->translate['zh-tw'] = '中文繁體'; +$lang->editor->translate['js'] = 'JS腳本'; +$lang->editor->translate['css'] = 'CSS樣式'; +$lang->editor->translate['ext'] = '擴展檔案'; + +$lang->editor->modules['action'] = '系統日誌'; +$lang->editor->modules['admin'] = '後台管理'; +$lang->editor->modules['api'] = 'API介面'; +$lang->editor->modules['bug'] = '缺陷管理'; +$lang->editor->modules['build'] = 'Build'; +$lang->editor->modules['common'] = '公有模組'; +$lang->editor->modules['company'] = '組織視圖'; +$lang->editor->modules['convert'] = '從其他系統導入'; +$lang->editor->modules['dept'] = '部門結構'; +$lang->editor->modules['doc'] = '文檔視圖'; +$lang->editor->modules['extension'] = '插件管理'; +$lang->editor->modules['file'] = '附件'; +$lang->editor->modules['group'] = '權限分組'; +$lang->editor->modules['index'] = '首頁'; +$lang->editor->modules['install'] = '安裝'; +$lang->editor->modules['misc'] = '雜項'; +$lang->editor->modules['my'] = '我的地盤'; +$lang->editor->modules['product'] = '產品視圖'; +$lang->editor->modules['productplan'] = '產品計劃'; +$lang->editor->modules['project'] = '項目視圖'; +$lang->editor->modules['qa'] = '測試視圖'; +$lang->editor->modules['release'] = '發佈'; +$lang->editor->modules['report'] = '報表'; +$lang->editor->modules['search'] = '搜索'; +$lang->editor->modules['story'] = '需求'; +$lang->editor->modules['task'] = '任務'; +$lang->editor->modules['testcase'] = '用例管理'; +$lang->editor->modules['testtask'] = '測試任務'; +$lang->editor->modules['todo'] = 'TODO'; +$lang->editor->modules['tree'] = '模組維護'; +$lang->editor->modules['upgrade'] = '升級'; +$lang->editor->modules['user'] = '用戶'; diff --git a/trunk/module/editor/model.php b/trunk/module/editor/model.php new file mode 100644 index 0000000000..4dd6337656 --- /dev/null +++ b/trunk/module/editor/model.php @@ -0,0 +1,561 @@ + + * @package editor + * @version $Id$ + * @link http://www.zentao.net + */ +class editorModel extends model +{ + private $pathFix = DIRECTORY_SEPARATOR; + + /** + * Get all modules + * + * @access public + * @return string + */ + public function getModules() + { + $modules = glob($this->app->getModuleRoot() . '*'); + $moduleList = '
        '; + foreach($modules as $module) + { + $module = basename($module); + if($module == 'editor' or $module == 'help' or $module == 'setting') continue; + $moduleName = !empty($this->lang->editor->modules[$module]) ? $this->lang->editor->modules[$module] : $module; + $moduleList .= '
      • ' . html::a(inlink('extend', "moduleDir=$module"), $moduleName, 'extendWin') . '
      • '; + } + $moduleList .= '
      '; + return $moduleList; + } + + /** + * Get module files, contain control's methods and model's method but except ext. + * + * @access public + * @return array + */ + public function getModuleFiles($moduleDir) + { + $moduleRoot = $this->app->getModuleRoot(); + $moduleFullDir = $moduleRoot . $moduleDir; + $moduleFiles = scandir($moduleFullDir); + foreach($moduleFiles as $moduleFile) + { + if($moduleFile == '.' or $moduleFile == '..' or $moduleFile == '.svn') continue; + $moduleFullFile = $moduleFullDir . $this->pathFix . $moduleFile; + if($moduleFile == 'control.php' or $moduleFile == 'model.php') + { + $allModules[$moduleFullDir][$moduleFullFile] = $this->analysis($moduleFullFile); + } + elseif($moduleFile == 'ext') + { + $allModules[$moduleFullDir][$moduleFullFile] = $this->getExtensionFiles($moduleFullFile); + } + elseif(is_dir($moduleFullFile)) + { + $ext = ($moduleFile == 'js' or $moduleFile == 'css') ? $moduleFile : 'php'; + foreach(glob($moduleFullFile . $this->pathFix . "*.$ext") as $fileName) $allModules[$moduleFullDir][$moduleFullFile][$fileName] = basename($fileName); + } + else + { + $allModules[$moduleFullDir][$moduleFullFile] = $moduleFile; + } + } + $allModules = $this->sortModule($moduleFullDir, $allModules); + return $allModules; + } + + /** + * Get extension files. + * + * @param int $extPath + * @access public + * @return void + */ + public function getExtensionFiles($extPath) + { + $extensionList = array(); + $extensionDirs = scandir($extPath); + foreach($extensionDirs as $extensionDir) + { + if($extensionDir == '.' or $extensionDir == '..' or $extensionDir == '.svn') continue; + $extensionFullDir = $extPath . $this->pathFix . $extensionDir; + if(is_dir($extensionFullDir)) + { + $extensionList[$extensionFullDir] = array(); + /* extend of lang is more a grade of directroy. */ + if($extensionDir == 'lang' or $extensionDir == 'js' or $extensionDir == 'css') + { + + $extensionList[$extensionFullDir] = $this->getTwoGradeFiles($extensionFullDir); + continue; + } + $extensionFiles = scandir($extensionFullDir); + foreach($extensionFiles as $extensionFile) + { + if($extensionFile == '.' or $extensionFile == '..' or $extensionFile == '.svn') continue; + $extensionFullFile = $extensionFullDir . $this->pathFix . $extensionFile; + $extensionList[$extensionFullDir][$extensionFullFile] = $extensionFile; + } + } + } + $extensionList = $this->sortModule($extPath, $extensionList, true); + return $extensionList; + } + + /** + * Sort module + * + * @param string $filePath + * @param string $moduleFiles + * @param bool $isExtension + * @access public + * @return array + */ + public function sortModule($filePath, $moduleFiles, $isExtension = false) + { + $sort = $isExtension ? 'extSort' : 'sort'; + $sortModules = array(); + if(!$isExtension) $moduleFiles = $moduleFiles[$filePath]; + foreach($this->config->editor->$sort as $sort) + { + $sortKey = empty($sort) ? $filePath : $filePath . $this->pathFix . $sort; + if(array_key_exists($sortKey, $moduleFiles)) $sortModules[$sortKey] = $moduleFiles[$sortKey]; + } + return $sortModules; + } + + /** + * if a directory has two grage, this method will get files + * + * @param string $extensionFullDir + * @access public + * @return string + */ + public function getTwoGradeFiles($extensionFullDir) + { + $fileList = array(); + $langDirs = scandir($extensionFullDir); + foreach($langDirs as $langDir) + { + if($langDir == '.' or $langDir == '..' or $langDir == '.svn') continue; + $langFullDir = $extensionFullDir . $this->pathFix . $langDir; + $fileList[$langFullDir] = array(); + if(is_dir($langFullDir)) + { + $langFiles = scandir($langFullDir); + foreach($langFiles as $langFile) + { + if($langFile == '.' or $langFile == '..' or $langFile == '.svn') continue; + $langFullFile = $langFullDir . $this->pathFix . $langFile; + $fileList[$langFullDir][$langFullFile] = $langFile; + } + } + } + return $fileList; + } + + /** + * Analysis methods of control and model. + * + * @param string $fileName + * @access public + * @return array + */ + public function analysis($fileName) + { + $classMethod = array(); + $class = strstr($fileName, $this->pathFix . 'module' . $this->pathFix); + $class = substr($class, 0, strpos($class, $this->pathFix, 9)); + $class = basename($class); + if(strpos($fileName, 'model.php') !== false) $class .= 'Model'; + if(!class_exists($class)) include $fileName; + $reflection = new ReflectionClass($class); + foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) + { + $methodName = $method->name; + if($method->getFileName() != $fileName) continue; + if($methodName == '__construct') continue; + $classMethod[$fileName . $this->pathFix . $methodName] = $methodName; + } + return $classMethod; + } + + /** + * Print tree from module files. + * + * @param int $files + * @access public + * @return void + */ + public function printTree($files, $isRoot = true) + { + if(empty($files) or !is_array($files)) return false; + $tree = $isRoot ? "
        \n" : "
          \n"; + if($isRoot) + { + $langFile = dirname(key($files)) . $this->pathFix . 'lang' . $this->pathFix . $this->cookie->lang. '.php'; + if(file_exists($langFile)) include_once $langFile; + $module = basename(dirname(key($files))); + if(isset($lang->$module)) + { + $this->module = $lang->$module; + } + elseif(isset($this->lang->$module)) + { + $this->module = $this->lang->$module; + } + else + { + $this->module = ''; + } + } + foreach($files as $key => $file) + { + $tree .= "
        • \n"; + if(is_array($file)) + { + $tree .= $this->addLink4Dir($key); + $tree .= $this->printTree($file, false); + } + else + { + $tree .= $this->addLink4File($key, $file); + } + $tree .= "
        • \n"; + } + $tree .= "
        \n"; + return $tree; + } + + /** + * Add link for directory or has children grade + * + * @param string $filePath + * @access public + * @return string + */ + public function addLink4Dir($filePath) + { + $tree = ''; + $fileName = basename($filePath); + if(isset($this->lang->editor->modules[$fileName])) + { + $file = "" . $this->lang->editor->modules[$fileName] . ''; + } + elseif(isset($this->lang->editor->translate[$fileName])) + { + $file = "" . $this->lang->editor->translate[$fileName] . ''; + } + else + { + $file = "$fileName"; + } + if(strpos($filePath, $this->pathFix . 'ext' . $this->pathFix) !== false) + { + switch($fileName) + { + case 'lang': $tree .= $file; break; + case 'js': $tree .= "$file " . html::a($this->getExtendLink($filePath, "newJS"), $this->lang->editor->newExtend, 'editWin'); break; + case 'css': $tree .= "$file " . html::a($this->getExtendLink($filePath, "newCSS"), $this->lang->editor->newExtend, 'editWin'); break; + default: $tree .= "$file " . html::a($this->getExtendLink($filePath, "newExtend"), $this->lang->editor->newExtend, 'editWin'); + } + } + elseif($fileName == 'model.php') + { + $tree .= "$file " . html::a($this->getExtendLink($filePath, 'newMethod'), $this->lang->editor->newMethod, 'editWin'); + } + elseif($fileName == 'control.php') + { + $tree .= "$file " . html::a(inlink('newPage', "filePath=" . helper::safe64Encode($filePath)), $this->lang->editor->newPage, 'editWin'); + } + else + { + $tree .= $file; + } + return $tree; + } + + /** + * Add link for file + * + * @param string $filePath + * @param string $file + * @access public + * @return string + */ + public function addLink4File($filePath, $file) + { + $tree = ''; + if(isset($this->module->$file) and !is_object($this->module->$file) and !is_array($this->module->$file)) + { + $file = "" . $this->module->$file . ''; + } + elseif(isset($this->lang->editor->translate[$file])) + { + if(strpos($filePath, $this->pathFix . 'ext' . $this->pathFix) !== false and $file == 'config.php') + { + $file = "$file"; + } + else + { + $file = "" . $this->lang->editor->translate[$file] . ''; + } + } + else + { + $file = "$file"; + } + if(strpos($filePath, $this->pathFix . 'ext' . $this->pathFix) !== false) + { + $tree .= "$file " . html::a($this->getExtendLink($filePath, "edit"), $this->lang->edit, 'editWin'); + $tree .= html::a(inlink('delete', 'path=' . helper::safe64Encode($filePath)), $this->lang->delete, 'hiddenwin') . "\n"; + } + elseif(basename(dirname($filePath))== 'view') + { + $tree .= "$file " . html::a($this->getExtendLink($filePath, "override"), $this->lang->editor->override, 'editWin'); + $tree .= html::a($this->getExtendLink($filePath, "newHook"), $this->lang->editor->newHook, 'editWin') . "\n"; + } + else + { + $parentDir = basename(dirname($filePath)); + $action = 'extendOther'; + if($parentDir == 'control.php') $action = 'extendControl'; + if($parentDir == 'model.php') $action = 'extendModel'; + $tree .= "$file " . html::a($this->getExtendLink($filePath, $action), $this->lang->editor->extend, 'editWin'); + if($parentDir == 'lang') $tree .= html::a($this->getExtendLink($filePath, "new" . str_replace('-', '_', basename($filePath, '.php'))), $this->lang->editor->newLang, 'editWin'); + if(basename($filePath) == 'config.php') $tree .= html::a($this->getExtendLink($filePath, "newConfig"), $this->lang->editor->newConfig, 'editWin'); + } + return $tree; + } + + /** + * Get extend link + * + * @param string $filePath + * @param string $action + * @param string $isExtends + * @access public + * @return string + */ + public function getExtendLink($filePath, $action, $isExtends = '') + { + return inlink('edit', "filePath=" . helper::safe64Encode($filePath) . "&action=$action&isExtends=$isExtends"); + } + + /** + * Save file to extension. + * + * @param string $filePath + * @access public + * @return void + */ + public function save($filePath) + { + $fileContent = $this->post->fileContent; + if(get_magic_quotes_gpc()) $fileContent = stripslashes($fileContent); + $dirPath = dirname($filePath); + $extFilePath = substr($filePath, 0, strpos($filePath, $this->pathFix . 'ext' . $this->pathFix) + 4); + if(!is_dir($dirPath) and is_writable($extFilePath)) mkdir($dirPath, 0777, true); + if(is_writable($dirPath)) + { + file_put_contents($filePath, $fileContent); + } + else + { + die(js::alert($this->lang->editor->notWritable . $extFilePath)); + } + } + + /** + * Extend model.php and get file content. + * + * @param string $filePath + * @access public + * @return string + */ + public function extendModel($filePath) + { + $className = basename(dirname(dirname($filePath))); + if(!class_exists($className)) include(dirname($filePath)); + $methodName = basename($filePath); + $methodParam = $this->getParam($className, $methodName, 'Model'); + return $fileContent = <<getParam($className, $methodName); + return $fileContent = <<getMethodCode($className, $methodName); + return $fileContent = <<pathFix . 'module' . $this->pathFix); + $className = substr($className, 0, strpos($className, $this->pathFix, 9)); + $className = basename($className); + $methodName = basename($filePath, '.php'); + return $fileContent = <<getParameters() as $param) + { + $methodParam .= '$' . $param->getName(); + if($param->isOptional()) + { + $defaultParam = $param->getDefaultValue(); + if(is_string($defaultParam)) $methodParam .= "='$defaultParam', "; + else $methodParam .= "=$defaultParam, "; + } + else + { + $methodParam .= ', '; + } + } + $methodParam = rtrim($methodParam, ', '); + return $methodParam; + } + + /** + * Get method code. + * + * @param string $className + * @param string $methodName + * @param string $ext value may be Model + * @access public + * @return string + */ + public function getMethodCode($className, $methodName, $ext = '') + { + $method = new ReflectionMethod($className . $ext, $methodName); + $fileName = $method->getFileName(); + $startLine = $method->getStartLine(); + $endLine = $method->getEndLine(); + $file = file($fileName); + $code = ''; + for($i = $startLine - 1; $i <= $endLine; $i++) + { + $code .= $file[$i]; + } + return $code; + } + + /** + * Get save path. + * + * @param string $filePath + * @param string $action + * @access public + * @return string + */ + public function getSavePath($filePath, $action) + { + $fileName = empty($_POST['fileName']) ? '' : trim($this->post->fileName); + $moduleName = strstr($filePath, $this->pathFix . 'module' . $this->pathFix); + $moduleName = substr($moduleName, 0, strpos($moduleName, $this->pathFix, 9)); + $moduleName = basename($moduleName); + $extPath = $this->app->getModuleRoot() . $moduleName . $this->pathFix . 'ext' . $this->pathFix; + switch($action) + { + case 'extendModel': + $fileName = empty($fileName) ? strtolower(basename($filePath)) . '.php' : $fileName; + return $extPath . 'model' . $this->pathFix . $fileName; + case 'extendControl': + $fileName = strtolower(basename($filePath)) . '.php'; + return $extPath . 'control' . $this->pathFix . $fileName; + case 'override': + $fileName = basename($filePath); + return $extPath . 'view' . $this->pathFix . $fileName; + case 'extendOther': + $editName = basename($filePath); + $fileName = empty($fileName) ? $editName: $fileName; + if($editName == 'config.php') return $extPath . 'config' .$this->pathFix . $fileName; + elseif(strpos($editName, '.php') !== false) return $extPath . 'lang' . $this->pathFix . str_replace('.php', '', $editName) . $this->pathFix . $fileName; + else return $extPath . substr($editName, strrpos($editName, '.') + 1) . $this->pathFix . substr($editName, 0, strrpos($editName, '.')) . $this->pathFix . $fileName; + default: + if(empty($fileName)) die(js::error($this->lang->editor->emptyFileName)); + $action = strtolower(str_replace('new', '', $action)); + if($action == 'hook') return $extPath . 'view' . $this->pathFix . $fileName; + elseif($action == 'method') return $extPath . basename($filePath, '.php') . $this->pathFix . $fileName; + elseif($action == 'extend') return $filePath . $this->pathFix . $fileName; + elseif($action == 'config') return $extPath . 'config' . $this->pathFix . $fileName; + elseif($action == 'js') return $extPath . 'js' . $this->pathFix . substr($fileName, 0, strrpos($fileName, '.')) . $this->pathFix . $fileName; + elseif($action == 'css') return $extPath . 'css' . $this->pathFix . substr($fileName, 0, strrpos($fileName, '.')) . $this->pathFix . $fileName; + else return $extPath . 'lang' . $this->pathFix . str_replace('_', '-', $action) . $this->pathFix . $fileName; + } + } +} diff --git a/trunk/module/editor/view/edit.html.php b/trunk/module/editor/view/edit.html.php new file mode 100644 index 0000000000..e4f641fe48 --- /dev/null +++ b/trunk/module/editor/view/edit.html.php @@ -0,0 +1,70 @@ + + * @package editor + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
        ' id='dataform'> + + + + + + + + + + + + + + + + +
        + {$lang->editor->filePath}"?> + +
        + " . $lang->editor->sourceFile . ''?>
        + +
        + +
        + + " . $lang->editor->fileName . ''?> + + editor->exampleHook; + } + elseif($action and $action == 'extendOther' and strpos(basename($filePath), '.js') !== false or $action == 'newJS') + { + echo $lang->editor->exampleJs; + } + elseif($action and $action == 'extendOther' and strpos(basename($filePath), '.css') !== false or $action == 'newCSS') + { + echo $lang->editor->exampleCss; + } + else + { + echo $lang->editor->examplePHP; + } + ?> + + + + editor->isOverride?> + +
        +
        + diff --git a/trunk/module/editor/view/extend.html.php b/trunk/module/editor/view/extend.html.php new file mode 100644 index 0000000000..5fae8ac265 --- /dev/null +++ b/trunk/module/editor/view/extend.html.php @@ -0,0 +1,29 @@ + + * @package editor + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + +
        editor->modules[$module])? $lang->editor->modules[$module] : $module;?>
        + + + + diff --git a/trunk/module/editor/view/index.html.php b/trunk/module/editor/view/index.html.php new file mode 100644 index 0000000000..873003bfef --- /dev/null +++ b/trunk/module/editor/view/index.html.php @@ -0,0 +1,24 @@ + + * @package editor + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + +
        +
        editor->moduleList?>
        +
        +
        + diff --git a/trunk/module/editor/view/newpage.html.php b/trunk/module/editor/view/newpage.html.php new file mode 100644 index 0000000000..a0062c7b95 --- /dev/null +++ b/trunk/module/editor/view/newpage.html.php @@ -0,0 +1,33 @@ + + * @package editor + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
        + + + + + + + + + + + +
        editor->newPage?>
        editor->filePath?>
        editor->pageName?> + editor->examplePHP; + ?> +
        +
        + diff --git a/trunk/module/extension/config.php b/trunk/module/extension/config.php new file mode 100644 index 0000000000..e2ae0dfc6a --- /dev/null +++ b/trunk/module/extension/config.php @@ -0,0 +1,3 @@ +extension->apiRoot = 'http://www.zentao.net/extension-'; +$config->extension->extPathes = array('module', 'bin', 'www', 'library', 'config'); diff --git a/trunk/module/extension/control.php b/trunk/module/extension/control.php new file mode 100644 index 0000000000..13f62812de --- /dev/null +++ b/trunk/module/extension/control.php @@ -0,0 +1,387 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +class extension extends control +{ + /** + * Browse extensions. + * + * @param string $status + * @access public + * @return void + */ + public function browse($status = 'installed') + { + $extensions = $this->extension->getLocalExtensions($status); + $versions = array(); + if($extensions and $status == 'installed') + { + /* Get latest release from remote. */ + $extCodes = helper::safe64Encode(join(',', array_keys($extensions))); + $results = $this->extension->getExtensionsByAPI('bycode', $extCodes, $recTotal = 0, $recPerPage = 1000, $pageID = 1); + if(isset($results->extensions)) + { + $remoteReleases = $results->extensions; + foreach($remoteReleases as $release) + { + if(!isset($extensions[$release->code])) continue; + + $extension = $extensions[$release->code]; + $extension->viewLink = $release->viewLink; + if($extension->version != $release->latestRelease->releaseVersion and $this->extension->checkVersion($release->latestRelease->zentaoVersion)) + { + $upgradeLink = inlink('upgrade', "extension=$release->code&downLink=" . helper::safe64Encode($release->latestRelease->downLink) . "&md5={$release->latestRelease->md5}&type=$release->type"); + $upgradeLink = ($release->latestRelease->charge or !$release->latestRelease->public) ? $release->latestRelease->downLink : $upgradeLink; + $extension->upgradeLink = $upgradeLink; + } + } + } + } + + $this->view->header->title = $this->lang->extension->browse; + $this->view->position[] = $this->lang->extension->browse; + $this->view->tab = $status; + $this->view->extensions = $extensions; + $this->view->versions = $versions; + $this->display(); + } + + /** + * Obtain extensions from the community. + * + * @param string $type + * @param string $param + * @access public + * @return void + */ + public function obtain($type = 'byUpdatedTime', $param = '', $recTotal = 0, $recPerPage = 10, $pageID = 1) + { + /* Init vars. */ + $type = strtolower($type); + $moduleID = $type == 'bymodule' ? (int)$param : 0; + $extensions = array(); + $pager = null; + + /* Set the key. */ + if($type == 'bysearch') $param = helper::safe64Encode($this->post->key); + + /* Get results from the api. */ + $results = $this->extension->getExtensionsByAPI($type, $param, $recTotal, $recPerPage, $pageID); + if($results) + { + $this->app->loadClass('pager', $static = true); + $pager = new pager($results->dbPager->recTotal, $results->dbPager->recPerPage, $results->dbPager->pageID); + $extensions = $results->extensions; + } + + $this->view->moduleTree = $this->extension->getModulesByAPI(); + $this->view->extensions = $extensions; + $this->view->installeds = $this->extension->getLocalExtensions('installed'); + $this->view->pager = $pager; + $this->view->tab = 'obtain'; + $this->view->type = $type; + $this->view->moduleID = $moduleID; + $this->display(); + } + + /** + * Install a extension + * + * @param string $extension + * @param string $downLink + * @param string $md5 + * @param string $type + * @param string $overridePackage + * @param string $ignoreCompatible + * @param string $overrideFile + * @param string $agreeLicense + * @param int $upgrade + * @access public + * @return void + */ + public function install($extension, $downLink = '', $md5 = '', $type = '', $overridePackage = 'no', $ignoreCompatible = 'no', $overrideFile = 'no', $agreeLicense = 'no', $upgrade = 'no') + { + $this->view->error = ''; + $installTitle = $upgrade == 'no' ? $this->lang->extension->install : $this->lang->extension->upgrade; + $installType = $upgrade == 'no' ? $this->lang->extension->installExt : $this->lang->extension->upgradeExt; + $this->view->installType = $installType; + $this->view->upgrade = $upgrade; + $this->view->header->title = $installTitle . $this->lang->colon . $extension; + + /* Get the package file name. */ + $packageFile = $this->extension->getPackageFile($extension); + + if($downLink) + { + /* Checking download path. */ + $return = $this->extension->checkDownloadPath(); + if($return->result != 'ok') + { + $this->view->error = $return->error; + die($this->display()); + } + + /* Check file exists or not. */ + if(file_exists($packageFile) and $overridePackage == 'no') + { + $overrideLink = inlink('install', "extension=$extension&downLink=$downLink&md5=$md5&type=$type&overridePackage=yes&ignoreCompatible=$ignoreCompatible&overrideFile=$overrideFile&agreeLicense=$agreeLicense&upgrade=$upgrade"); + $this->view->error = sprintf($this->lang->extension->errorPackageFileExists, $packageFile, $installType, $overrideLink); + die($this->display()); + } + + /* Download the package file. */ + $this->extension->downloadPackage($extension, helper::safe64Decode($downLink)); + if(!file_exists($packageFile)) + { + $this->view->error = sprintf($this->lang->extension->errorDownloadFailed, $packageFile); + die($this->display()); + } + elseif($md5 != '' and md5_file($packageFile) != $md5) + { + unlink($packageFile); + $this->view->error = sprintf($this->lang->extension->errorMd5Checking, $packageFile); + die($this->display()); + } + } + + /* Check the package file exists or not. */ + if(!file_exists($packageFile)) + { + $this->view->error = sprintf($this->lang->extension->errorPackageNotFound, $packageFile); + die($this->display()); + } + + /* Checking the extension pathes. */ + $return = $this->extension->checkExtensionPathes($extension); + if($this->session->dirs2Created == false) $this->session->set('dirs2Created', $return->dirs2Created); // Save the dirs to be created. + if($return->result != 'ok') + { + $this->view->error = $return->errors; + die($this->display()); + } + + /* Extract the package. */ + $return = $this->extension->extractPackage($extension); + if($return->result != 'ok') + { + $this->view->error = sprintf($this->lang->extension->errorExtracted, $packageFile, $return->error); + die($this->display()); + } + + /* Check version comptiable. */ + $zentaoVersion = $this->extension->getZentaoVersion($extension); + if(!$this->extension->checkVersion($zentaoVersion) and $ignoreCompatible == 'no') + { + $ignoreLink = inlink('install', "extension=$extension&downLink=$downLink&md5=$md5&type=$type&overridePackage=$overridePackage&ignoreCompatible=yes&overrideFile=$overrideFile&agreeLicense=$agreeLicense&upgrade=$upgrade"); + $returnLink = inlink('obtain'); + $this->view->error = sprintf($this->lang->extension->errorCheckIncompatible, $installType, $ignoreLink, $installType, $returnLink); + die($this->display()); + } + + /* Check files in the package conflicts with exists files or not. */ + if($overrideFile == 'no') + { + $return = $this->extension->checkFile($extension); + if($return->result != 'ok') + { + $overrideLink = inlink('install', "extension=$extension&downLink=$downLink&md5=$md5&type=$type&overridePackage=$overridePackage&ignoreCompatible=$ignoreCompatible&overrideFile=yes&agreeLicense=$agreeLicense&upgrade=$upgrade"); + $returnLink = inlink('obtain'); + $this->view->error = sprintf($this->lang->extension->errorFileConflicted, $return->error, $overrideLink, $returnLink); + die($this->display()); + } + } + + if($upgrade == 'yes') + { + $newInfo = $this->extension->parseExtensionCFG($extension); + $this->post->upgradeVersion = isset($newInfo->version) ? $newInfo->version : ''; + $oldInfo = $this->extension->getInfoFromDB($extension); + $this->post->installedVersion = $oldInfo ? $oldInfo->version : ''; + } + + /* Print the license form. */ + if($agreeLicense == 'no') + { + $extensionInfo = $this->extension->getInfoFromPackage($extension); + $license = $this->extension->processLicense($extensionInfo->license); + $agreeLink = inlink('install', "extension=$extension&downLink=$downLink&md5=$md5&type=$type&overridePackage=$overridePackage&ignoreCompatible=$ignoreCompatible&overrideFile=$overrideFile&agreeLicense=yes&upgrade=$upgrade"); + $this->view->license = $license; + $this->view->author = $extensionInfo->author; + $this->view->agreeLink = $agreeLink; + die($this->display()); + } + + /* The preInstall hook file. */ + $hook = $upgrade == 'yes' ? 'preupgrade' : 'preinstall'; + if($preHookFile = $this->extension->getHookFile($extension, $hook)) include $preHookFile; + + /* Save to database. */ + $this->extension->saveExtension($extension, $type); + + /* Copy files to target directory. */ + $this->view->files = $this->extension->copyPackageFiles($extension); + + /* Judge need execute db install or not. */ + $data->status = 'installed'; + $data->dirs = $this->session->dirs2Created; + $data->files = $this->view->files; + $data->installedTime = helper::now(); + $this->session->set('dirs2Created', array()); // clean the session. + + /* Execute the install.sql. */ + if($upgrade == 'no' and $this->extension->needExecuteDB($extension, 'install')) + { + $return = $this->extension->executeDB($extension, 'install'); + if($return->result != 'ok') + { + $this->view->error = sprintf($this->lang->extension->errorInstallDB, $return->error); + die($this->display()); + } + } + + /* Update status, dirs, files and installed time. */ + $this->extension->updateExtension($extension, $data); + $this->view->downloadedPackage = !empty($downLink); + + /* The postInstall hook file. */ + $hook = $upgrade == 'yes' ? 'postupgrade' : 'postinstall'; + if($postHookFile = $this->extension->getHookFile($extension, $hook)) include $postHookFile; + + $this->display(); + } + + /** + * Uninstall an extension. + * + * @param string $extension + * @access public + * @return void + */ + public function uninstall($extension) + { + if($preUninstallHook = $this->extension->getHookFile($extension, 'preuninstall')) include $preUninstallHook; + + $this->extension->executeDB($extension, 'uninstall'); + $this->extension->updateExtension($extension, array('status' => 'available')); + $this->view->removeCommands = $this->extension->removePackage($extension); + $this->view->header->title = $this->lang->extension->uninstallFinished; + + if($postUninstallHook = $this->extension->getHookFile($extension, 'postuninstall')) include $postUninstallHook; + $this->display(); + } + + /** + * Activate an extension; + * + * @param string $extension + * @access public + * @return void + */ + public function activate($extension, $ignore = 'no') + { + if($ignore == 'no') + { + $return = $this->extension->checkFile($extension); + if($return->result != 'ok') + { + $ignoreLink = inlink('activate', "extension=$extension&ignore=yes"); + $resetLink = inlink('browse', 'type=deactivated'); + $this->view->error = sprintf($this->lang->extension->errorFileConflicted, $return->error, $ignoreLink, $resetLink); + die($this->display()); + } + } + + $this->extension->copyPackageFiles($extension); + $this->extension->updateExtension($extension, array('status' => 'installed')); + $this->view->header->title = $this->lang->extension->activateFinished; + $this->display(); + } + + /** + * Deactivate an extension + * + * @param string $extension + * @access public + * @return void + */ + public function deactivate($extension) + { + $this->extension->updateExtension($extension, array('status' => 'deactivated')); + $this->view->removeCommands = $this->extension->removePackage($extension); + $this->view->header->title = $this->lang->extension->deactivateFinished; + $this->display(); + } + + /** + * Upload an extension + * + * @access public + * @return void + */ + public function upload() + { + if($_FILES) + { + $tmpName = $_FILES['file']['tmp_name']; + $fileName = $_FILES['file']['name']; + $extension = basename($fileName, '.zip'); + move_uploaded_file($tmpName, $this->app->getTmpRoot() . "/extension/$fileName"); + $info = $this->extension->getInfoFromDB($extension); + $type = $info->status == 'installed' ? 'upgrade' : 'install'; + $link = $type == 'install' ? inlink('install', "extension=$extension") : inlink('upgrade', "extension=$extension"); + $this->locate($link); + } + $this->display(); + } + + /** + * Erase an extension. + * + * @param string $extension + * @access public + * @return void + */ + public function erase($extension) + { + $this->view->removeCommands = $this->extension->erasePackage($extension); + $this->view->header->title = $this->lang->extension->eraseFinished; + $this->display(); + } + + /** + * Update extension. + * + * @param string $extension + * @param string $downLink + * @param string $md5 + * @param string $type + * @access public + * @return void + */ + public function upgrade($extension, $downLink = '', $md5 = '', $type = '') + { + $this->extension->removePackage($extension); + $this->locate(inlink('install', "extension=$extension&downLink=$downLink&md5=$md5&type=$type&overridePackage=no&ignoreCompatible=yes&overrideFile=no&agreeLicense=no&upgrade=yes")); + } + + /** + * Browse the structure of extension. + * + * @param int $extension + * @access public + * @return void + */ + public function structure($extension) + { + $this->view->extension = $this->extension->getInfoFromDB($extension); + $this->display(); + } +} diff --git a/trunk/module/extension/css/common.css b/trunk/module/extension/css/common.css new file mode 100644 index 0000000000..fa4b6e8ca0 --- /dev/null +++ b/trunk/module/extension/css/common.css @@ -0,0 +1,13 @@ +.active a {color:blue; font-weight:bold;} +.button-c {padding:1px 3px; border:1px solid gray; background:#e4e4ef; color:black; text-decoration:none} +.error {color:red; font-size:14px} +.success {color:green; font-size:14px} + +.exttable td {padding:10px 5px 5px 10px} +.exttable caption{background:#efefef; border-color:#CCC9C9; margin-left:0} +.exttable {border-color:#CCC9C9; border-top:none;} +.exttable td{border-color:#CCC9C9} + +#license{width:100%; height:190px; border:1px solid #e4e4e4} + +small{font-weight:normal} diff --git a/trunk/module/extension/css/upload.css b/trunk/module/extension/css/upload.css new file mode 100644 index 0000000000..a57fe9391b --- /dev/null +++ b/trunk/module/extension/css/upload.css @@ -0,0 +1 @@ +form{margin-top:150px} diff --git a/trunk/module/extension/js/common.js b/trunk/module/extension/js/common.js new file mode 100644 index 0000000000..ce34126492 --- /dev/null +++ b/trunk/module/extension/js/common.js @@ -0,0 +1,3 @@ +if($('a.iframe').size()) $("a.iframe").colorbox({width:800, height:400, iframe:true, transition:'elastic', speed:350, scrolling:true}); +if($('a.manual').size()) $("a.manual").colorbox({width:1024, height:600, iframe:true, transition:'elastic', speed:350, scrolling:false}); +if($('a.extension').size()) $("a.extension").colorbox({width:1024, height:480, iframe:true, transition:'elastic', speed:350, scrolling:true}); diff --git a/trunk/module/extension/lang/en.php b/trunk/module/extension/lang/en.php new file mode 100644 index 0000000000..af2485137d --- /dev/null +++ b/trunk/module/extension/lang/en.php @@ -0,0 +1,98 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->extension->common = 'Extension'; +$lang->extension->browse = 'Browse'; +$lang->extension->install = 'Install'; +$lang->extension->installAuto = 'AutoInstall'; +$lang->extension->installForce = 'ForceInstall'; +$lang->extension->uninstall = 'Uninstall'; +$lang->extension->activate = 'Activate'; +$lang->extension->deactivate = 'Deactivate'; +$lang->extension->obtain = 'Obtain'; +$lang->extension->view = 'Info'; +$lang->extension->download = 'Download'; +$lang->extension->downloadAB = 'Down'; +$lang->extension->upload = 'Upload and install'; +$lang->extension->erase = 'Erase'; +$lang->extension->upgrade = 'Upgrade Extension'; +$lang->extension->agreeLicense = 'I agree the license'; + +$lang->extension->structure = 'Structure'; +$lang->extension->installed = 'Installed'; +$lang->extension->deactivated = 'Deactivated'; +$lang->extension->available = 'Downloaded'; + +$lang->extension->id = 'ID'; +$lang->extension->name = 'Name'; +$lang->extension->code = 'Code'; +$lang->extension->version = 'Version'; +$lang->extension->compatible = 'Compatible'; +$lang->extension->latest = 'Latest:%s,need zentao %s'; +$lang->extension->author = 'Author'; +$lang->extension->license = 'License'; +$lang->extension->intro = 'Description'; +$lang->extension->abstract = 'Abstract'; +$lang->extension->site = 'Site'; +$lang->extension->addedTime = 'Added Time'; +$lang->extension->updatedTime = 'Updated Time'; +$lang->extension->downloads = 'Downloads'; +$lang->extension->public = 'Public'; +$lang->extension->compatible = 'Compatible'; +$lang->extension->grade = 'Grade'; + +$lang->extension->publicList[0] = 'Manually'; +$lang->extension->publicList[1] = 'Auto'; + +$lang->extension->compatibleList[0] = 'Unknow'; +$lang->extension->compatibleList[1] = 'Compatible'; + +$lang->extension->byDownloads = 'Downloads'; +$lang->extension->byAddedTime = 'New added'; +$lang->extension->byUpdatedTime = 'Last updated'; +$lang->extension->bySearch = 'Search'; +$lang->extension->byCategory = 'By Category'; + +$lang->extension->installFailed = '%s failed, the reason is:'; +$lang->extension->installFinished = 'Good, the extension has been %s successfully.'; +$lang->extension->refreshPage = 'Refresh'; +$lang->extension->uninstallFinished = 'Extension has been successfully uninstalled.'; +$lang->extension->deactivateFinished = 'Extension has been successfully deactivated.'; +$lang->extension->activateFinished = 'Extension has been successfully activated.'; +$lang->extension->eraseFinished = 'Extension has been successfully erased.'; +$lang->extension->unremovedFiles = 'There are some unremoved files, you need remove them manually'; +$lang->extension->executeCommands = '

        Execute the following commands to fix them:

        '; +$lang->extension->successDownloadedPackage = 'Successfully downloaded the package file.'; +$lang->extension->successCopiedFiles = 'Successfully copied files. '; +$lang->extension->successInstallDB = 'Successfully installed database.'; +$lang->extension->viewInstalled = 'View installed extensions.'; +$lang->extension->viewAvailable = 'View available extensions'; +$lang->extension->viewDeactivated = 'View deactivated extensions'; + +$lang->extension->upgradeExt = 'Upgrade'; +$lang->extension->installExt = 'Install'; +$lang->extension->upgradeVersion = '(Upgrade from %s to %s)'; + +$lang->extension->errorOccurs = 'Error:'; +$lang->extension->errorGetModules = "Get extensions' categories data from the www.zentao.net failed. "; +$lang->extension->errorGetExtensions = 'Get extensions from www.zentao.net failed. You can visit
        www.zentao.net to find your extensions, download it manually and then upload to zentaopms to install it.'; +$lang->extension->errorDownloadPathNotFound = 'The save path of package file %sdoes not exists.
        For linux users, can execute mkdir -p %s to fix it.'; +$lang->extension->errorDownloadPathNotWritable = 'The save path of package file %sis not writable.
        For linux users, can execute sudo chmod 777 %s to fix it.'; +$lang->extension->errorPackageFileExists = 'There is already a file with the same name %s.

        If you want to %s again, please click this link.

        '; +$lang->extension->errorDownloadFailed = 'Download failed, please try again. Or you can download it manually and upload it to install.'; +$lang->extension->errorMd5Checking = 'The downloawd files checking failed, Please download it manually and upload it to install'; +$lang->extension->errorExtracted = 'The package file %s extracted failed. The error is:
        %s'; +$lang->extension->errorCheckIncompatible = 'This extenion is not compatible with current zentao version.

        You can Force %s or Cancel

        .'; +$lang->extension->errorFileConflicted = 'There are some files conflicted:
        %s

        You can Overide them or Cancel

        .'; +$lang->extension->errorPackageNotFound = 'The package file %s not found, perhaps download failed, try again.'; +$lang->extension->errorTargetPathNotWritable = 'Target path %s not writable.'; +$lang->extension->errorTargetPathNotExists = 'Target path %s not exists'; +$lang->extension->errorInstallDB = 'Execute database sql failed, the error is: %s'; diff --git a/trunk/module/extension/lang/zh-cn.php b/trunk/module/extension/lang/zh-cn.php new file mode 100644 index 0000000000..5eba7b3c9a --- /dev/null +++ b/trunk/module/extension/lang/zh-cn.php @@ -0,0 +1,98 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->extension->common = '插件管理'; +$lang->extension->browse = '浏览插件'; +$lang->extension->install = '安装插件'; +$lang->extension->installAuto = '自动安装'; +$lang->extension->installForce = '强制安装'; +$lang->extension->uninstall = '卸载'; +$lang->extension->activate = '激活'; +$lang->extension->deactivate = '禁用'; +$lang->extension->obtain = '获得插件'; +$lang->extension->view = '详情'; +$lang->extension->download = '下载插件'; +$lang->extension->downloadAB = '下载'; +$lang->extension->upload = '本地安装'; +$lang->extension->erase = '清除'; +$lang->extension->upgrade = '升级插件'; +$lang->extension->agreeLicense = '我同意该授权'; + +$lang->extension->structure = '目录结构'; +$lang->extension->installed = '已安装'; +$lang->extension->deactivated = '被禁用'; +$lang->extension->available = '已下载'; + +$lang->extension->id = '编号'; +$lang->extension->name = '名称'; +$lang->extension->code = '插件代号'; +$lang->extension->version = '版本'; +$lang->extension->compatible = '适用版本'; +$lang->extension->latest = '最新版本%s,兼容禅道%s'; +$lang->extension->author = '作者'; +$lang->extension->license = '授权'; +$lang->extension->intro = '详情'; +$lang->extension->abstract = '简介'; +$lang->extension->site = '官网'; +$lang->extension->addedTime = '添加时间'; +$lang->extension->updatedTime = '更新时间'; +$lang->extension->downloads = '下载量'; +$lang->extension->public = '下载方式'; +$lang->extension->compatible = '兼容性'; +$lang->extension->grade = '评分'; + +$lang->extension->publicList[0] = '手工下载'; +$lang->extension->publicList[1] = '直接下载'; + +$lang->extension->compatibleList[0] = '未知'; +$lang->extension->compatibleList[1] = '兼容'; + +$lang->extension->byDownloads = '最多下载'; +$lang->extension->byAddedTime = '最新添加'; +$lang->extension->byUpdatedTime = '最近更新'; +$lang->extension->bySearch = '搜索'; +$lang->extension->byCategory = '分类浏览'; + +$lang->extension->installFailed = '%s失败,错误原因如下:'; +$lang->extension->installFinished = '恭喜您,插件顺利的%s成功!'; +$lang->extension->refreshPage = '刷新页面'; +$lang->extension->uninstallFinished = '插件已经成功卸载'; +$lang->extension->deactivateFinished = '插件已经成功禁用'; +$lang->extension->activateFinished = '插件已经成功激活'; +$lang->extension->eraseFinished = '插件已经成功清除'; +$lang->extension->unremovedFiles = '有一些文件或目录未能删除,需要手工删除'; +$lang->extension->executeCommands = '

        执行下面的命令来修正这些问题:

        '; +$lang->extension->successDownloadedPackage = '成功下载插件'; +$lang->extension->successCopiedFiles = '成功拷贝文件'; +$lang->extension->successInstallDB = '成功安装数据库'; +$lang->extension->viewInstalled = '查看已安装插件'; +$lang->extension->viewAvailable = '查看可安装插件'; +$lang->extension->viewDeactivated = '查看已禁用插件'; + +$lang->extension->upgradeExt = '升级'; +$lang->extension->installExt = '安装'; +$lang->extension->upgradeVersion = '(从%s升级到%s)'; + +$lang->extension->errorOccurs = '错误:'; +$lang->extension->errorGetModules = '从www.zentao.net获得插件分类失败。可能是因为网络方面的原因,请检查后重新刷新页面。'; +$lang->extension->errorGetExtensions = '从www.zentao.net获得插件失败。可能是因为网络方面的原因,您可以到
        www.zentao.net手工下载插件,然后上传安装。'; +$lang->extension->errorDownloadPathNotFound = '插件下载存储路径%s不存在。
        linux下面请执行命令:mkdir -p %s来修正。'; +$lang->extension->errorDownloadPathNotWritable = '插件下载存储路径%s不可写。
        linux下面请执行命令:sudo chmod 777 %s来修正。'; +$lang->extension->errorPackageFileExists = '下载路径已经有一个名为的%s附件。

        重新%s,请点击此链接

        '; +$lang->extension->errorDownloadFailed = '下载失败,请重新下载。如果多次重试还不行,请尝试手工下载,然后通过上传功能上传。'; +$lang->extension->errorMd5Checking = '下载文件不完整,请重新下载。如果多次重试还不行,请尝试手工下载,然后通过上传功能上传。'; +$lang->extension->errorExtracted = '包文件 %s 解压缩失败,可能不是一个有效的zip文件。错误信息如下:
        %s'; +$lang->extension->errorCheckIncompatible = '该插件与禅道版本不兼容,%s后可能无法使用。。

        您可以选择 强制%s 或者 取消

        '; +$lang->extension->errorFileConflicted = '有以下文件冲突:
        %s

        您可以选择 覆盖 或者 取消

        '; +$lang->extension->errorPackageNotFound = '包文件 %s 没有找到,可能是因为自动下载失败。您可以尝试再次下载。'; +$lang->extension->errorTargetPathNotWritable = '目标路径 %s 不可写。'; +$lang->extension->errorTargetPathNotExists = '目标路径 %s 不存在。'; +$lang->extension->errorInstallDB = '执行数据库语句失败。错误信息如下:%s'; diff --git a/trunk/module/extension/lang/zh-tw.php b/trunk/module/extension/lang/zh-tw.php new file mode 100644 index 0000000000..c41ed9bef2 --- /dev/null +++ b/trunk/module/extension/lang/zh-tw.php @@ -0,0 +1,98 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->extension->common = '插件管理'; +$lang->extension->browse = '瀏覽插件'; +$lang->extension->install = '安裝插件'; +$lang->extension->installAuto = '自動安裝'; +$lang->extension->installForce = '強制安裝'; +$lang->extension->uninstall = '卸載'; +$lang->extension->activate = '激活'; +$lang->extension->deactivate = '禁用'; +$lang->extension->obtain = '獲得插件'; +$lang->extension->view = '詳情'; +$lang->extension->download = '下載插件'; +$lang->extension->downloadAB = '下載'; +$lang->extension->upload = '本地安裝'; +$lang->extension->erase = '清除'; +$lang->extension->upgrade = '升級插件'; +$lang->extension->agreeLicense = '我同意該授權'; + +$lang->extension->structure = '目錄結構'; +$lang->extension->installed = '已安裝'; +$lang->extension->deactivated = '被禁用'; +$lang->extension->available = '已下載'; + +$lang->extension->id = '編號'; +$lang->extension->name = '名稱'; +$lang->extension->code = '插件代號'; +$lang->extension->version = '版本'; +$lang->extension->compatible = '適用版本'; +$lang->extension->latest = '最新版本%s,兼容禪道%s'; +$lang->extension->author = '作者'; +$lang->extension->license = '授權'; +$lang->extension->intro = '詳情'; +$lang->extension->abstract = '簡介'; +$lang->extension->site = '官網'; +$lang->extension->addedTime = '添加時間'; +$lang->extension->updatedTime = '更新時間'; +$lang->extension->downloads = '下載量'; +$lang->extension->public = '下載方式'; +$lang->extension->compatible = '兼容性'; +$lang->extension->grade = '評分'; + +$lang->extension->publicList[0] = '手工下載'; +$lang->extension->publicList[1] = '直接下載'; + +$lang->extension->compatibleList[0] = '未知'; +$lang->extension->compatibleList[1] = '兼容'; + +$lang->extension->byDownloads = '最多下載'; +$lang->extension->byAddedTime = '最新添加'; +$lang->extension->byUpdatedTime = '最近更新'; +$lang->extension->bySearch = '搜索'; +$lang->extension->byCategory = '分類瀏覽'; + +$lang->extension->installFailed = '%s失敗,錯誤原因如下:'; +$lang->extension->installFinished = '恭喜您,插件順利的%s成功!'; +$lang->extension->refreshPage = '刷新頁面'; +$lang->extension->uninstallFinished = '插件已經成功卸載'; +$lang->extension->deactivateFinished = '插件已經成功禁用'; +$lang->extension->activateFinished = '插件已經成功激活'; +$lang->extension->eraseFinished = '插件已經成功清除'; +$lang->extension->unremovedFiles = '有一些檔案或目錄未能刪除,需要手工刪除'; +$lang->extension->executeCommands = '

        執行下面的命令來修正這些問題:

        '; +$lang->extension->successDownloadedPackage = '成功下載插件'; +$lang->extension->successCopiedFiles = '成功拷貝檔案'; +$lang->extension->successInstallDB = '成功安裝資料庫'; +$lang->extension->viewInstalled = '查看已安裝插件'; +$lang->extension->viewAvailable = '查看可安裝插件'; +$lang->extension->viewDeactivated = '查看已禁用插件'; + +$lang->extension->upgradeExt = '升級'; +$lang->extension->installExt = '安裝'; +$lang->extension->upgradeVersion = '(從%s升級到%s)'; + +$lang->extension->errorOccurs = '錯誤:'; +$lang->extension->errorGetModules = '從www.zentao.net獲得插件分類失敗。可能是因為網絡方面的原因,請檢查後重新刷新頁面。'; +$lang->extension->errorGetExtensions = '從www.zentao.net獲得插件失敗。可能是因為網絡方面的原因,您可以到
        www.zentao.net手工下載插件,然後上傳安裝。'; +$lang->extension->errorDownloadPathNotFound = '插件下載存儲路徑%s不存在。
        linux下面請執行命令:mkdir -p %s來修正。'; +$lang->extension->errorDownloadPathNotWritable = '插件下載存儲路徑%s不可寫。
        linux下面請執行命令:sudo chmod 777 %s來修正。'; +$lang->extension->errorPackageFileExists = '下載路徑已經有一個名為的%s附件。

        重新%s,請點擊此連結

        '; +$lang->extension->errorDownloadFailed = '下載失敗,請重新下載。如果多次重試還不行,請嘗試手工下載,然後通過上傳功能上傳。'; +$lang->extension->errorMd5Checking = '下載檔案不完整,請重新下載。如果多次重試還不行,請嘗試手工下載,然後通過上傳功能上傳。'; +$lang->extension->errorExtracted = '包檔案 %s 解壓縮失敗,可能不是一個有效的zip檔案。錯誤信息如下:
        %s'; +$lang->extension->errorCheckIncompatible = '該插件與禪道版本不兼容,%s後可能無法使用。。

        您可以選擇 強制%s 或者 取消

        '; +$lang->extension->errorFileConflicted = '有以下檔案衝突:
        %s

        您可以選擇 覆蓋 或者 取消

        '; +$lang->extension->errorPackageNotFound = '包檔案 %s 沒有找到,可能是因為自動下載失敗。您可以嘗試再次下載。'; +$lang->extension->errorTargetPathNotWritable = '目標路徑 %s 不可寫。'; +$lang->extension->errorTargetPathNotExists = '目標路徑 %s 不存在。'; +$lang->extension->errorInstallDB = '執行資料庫語句失敗。錯誤信息如下:%s'; diff --git a/trunk/module/extension/license/apache.txt b/trunk/module/extension/license/apache.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/trunk/module/extension/license/apache.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/trunk/module/extension/license/bsd.txt b/trunk/module/extension/license/bsd.txt new file mode 100644 index 0000000000..c257c45e43 --- /dev/null +++ b/trunk/module/extension/license/bsd.txt @@ -0,0 +1,25 @@ +Copyright . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of . diff --git a/trunk/module/extension/license/gpl.txt b/trunk/module/extension/license/gpl.txt new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/trunk/module/extension/license/gpl.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/trunk/module/extension/license/lgpl.txt b/trunk/module/extension/license/lgpl.txt new file mode 100644 index 0000000000..65c5ca88a6 --- /dev/null +++ b/trunk/module/extension/license/lgpl.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/trunk/module/extension/license/mit.txt b/trunk/module/extension/license/mit.txt new file mode 100644 index 0000000000..48114d6a1e --- /dev/null +++ b/trunk/module/extension/license/mit.txt @@ -0,0 +1,19 @@ +Copyright (C) by + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/trunk/module/extension/model.php b/trunk/module/extension/model.php new file mode 100644 index 0000000000..480bb4751c --- /dev/null +++ b/trunk/module/extension/model.php @@ -0,0 +1,755 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +class extensionModel extends model +{ + /** + * The extension manager version. Don't change it. + */ + const EXT_MANAGER_VERSION = '1.3'; + + /** + * The api agent(use snoopy). + * + * @var object + * @access public + */ + public $agent; + + /** + * The api root. + * + * @var string + * @access public + */ + public $apiRoot; + + /** + * The construct function. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->setAgent(); + $this->setApiRoot(); + $this->classFile = $this->app->loadClass('file'); + } + + /** + * Set the api agent. + * + * @access public + * @return void + */ + public function setAgent() + { + $this->agent = $this->app->loadClass('snoopy'); + } + + /** + * Set the apiRoot. + * + * @access public + * @return void + */ + public function setApiRoot() + { + $this->apiRoot = $this->config->extension->apiRoot; + } + + /** + * Fetch data from an api. + * + * @param string $url + * @access public + * @return mixed + */ + public function fetchAPI($url) + { + $url .= '?lang=' . str_replace('-', '_', $this->app->getClientLang()) . '&managerVersion=' . self::EXT_MANAGER_VERSION . '&zentaoVersion=' . $this->config->version; + $this->agent->fetch($url); + $result = json_decode($this->agent->results); + + if(!isset($result->status)) return false; + if($result->status != 'success') return false; + if(isset($result->data) and md5($result->data) != $result->md5) return false; + if(isset($result->data)) return json_decode($result->data); + } + + /** + * Get extension modules from the api. + * + * @access public + * @return string|bool + */ + public function getModulesByAPI() + { + $requestType = $this->config->requestType; + $webRoot = helper::safe64Encode($this->config->webRoot); + $apiURL = $this->apiRoot . 'apiGetmodules-' . $requestType . '-' . $webRoot . '.json'; + $data = $this->fetchAPI($apiURL); + if(isset($data->modules)) return $data->modules; + return false; + } + + /** + * Get extensions by some condition. + * + * @param string $type + * @param mixed $param + * @access public + * @return array|bool + */ + public function getExtensionsByAPI($type, $param, $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $apiURL = $this->apiRoot . "apiGetExtensions-$type-$param-$recTotal-$recPerPage-$pageID.json"; + $data = $this->fetchAPI($apiURL); + if(isset($data->extensions)) + { + foreach($data->extensions as $extension) + { + $extension->currentRelease = isset($extension->compatibleRelease) ? $extension->compatibleRelease : $extension->latestRelease; + $extension->currentRelease->compatible = isset($extension->compatibleRelease); + } + return $data; + } + return false; + } + + /** + * Get versions for some extensions. + * + * @param string $extensions + * @access public + * @return array|bool + */ + public function getVersionsByAPI($extensions) + { + $extensions = helper::safe64Encode($extensions); + $apiURL = $this->apiRoot . 'apiGetVersions-' . $extensions . '.json'; + $data = $this->fetchAPI($apiURL); + if(isset($data->versions)) return (array)$data->versions; + return false; + } + + /** + * Download an extension. + * + * @param string $extension + * @param string $downLink + * @access public + * @return void + */ + public function downloadPackage($extension, $downLink) + { + $packageFile = $this->getPackageFile($extension); + $this->agent->fetch($downLink); + file_put_contents($packageFile, $this->agent->results); + } + + /** + * Get extensions by status. + * + * @param string $status + * @access public + * @return array + */ + public function getLocalExtensions($status) + { + $extensions = $this->dao->select('*')->from(TABLE_EXTENSION)->where('status')->eq($status)->fi()->fetchAll('code'); + foreach($extensions as $extension) + { + if($extension->site and stripos(strtolower($extension->site), 'http') === false) $extension->site = 'http://' . $extension->site; + } + return $extensions; + } + + /** + * Get extension info from database. + * + * @param string $extension + * @access public + * @return object + */ + public function getInfoFromDB($extension) + { + return $this->dao->select('*')->from(TABLE_EXTENSION)->where('code')->eq($extension)->fetch(); + } + + /** + * Get info of an extension from the package file. + * + * @param string $extension + * @access public + * @return object + */ + public function getInfoFromPackage($extension) + { + /* Init the data. */ + $data->name = $extension; + $data->code = $extension; + $data->version = 'unknown'; + $data->author = 'unknown'; + $data->desc = $extension; + $data->site = 'unknown'; + $data->license = 'unknown'; + $data->zentaoVersion = ''; + + $info = $this->parseExtensionCFG($extension); + foreach($info as $key => $value) if(isset($data->$key)) $data->$key = $value; + if(isset($info->zentaoversion)) $data->zentaoVersion = $info->zentaoversion; + + return $data; + } + + /** + * Parse extension's config file. + * + * @param string $extension + * @access public + * @return object + */ + public function parseExtensionCFG($extension) + { + $info = new stdclass(); + + /* First, try ini file. before 2.5 version. */ + $infoFile = "ext/$extension/doc/copyright.txt"; + if(file_exists($infoFile)) return (object)parse_ini_file($infoFile); + + /** + * Then try parse yaml file. since 2.5 version. + */ + + /* Try the yaml of current lang, then try en. */ + $lang = $this->app->getClientLang(); + $infoFile = "ext/$extension/doc/$lang.yaml"; + if(!file_exists($infoFile)) $infoFile = "ext/$extension/doc/en.yaml"; + if(!file_exists($infoFile)) return $info; + + /* Load the yaml file and parse it into object. */ + $this->app->loadClass('spyc', true); + $info = (object)spyc_load(file_get_contents($infoFile)); + if(isset($info->releases)) + { + krsort($info->releases); + $info->version = key($info->releases); + foreach($info->releases[$info->version] as $key => $value) $info->$key = $value; + } + return $info; + } + + /** + * Get the full path of the zip file of a extension. + * + * @param string $extension + * @access public + * @return string + */ + public function getPackageFile($extension) + { + return $this->app->getTmpRoot() . 'extension/' . $extension . '.zip'; + } + + /** + * Get pathes from an extension package. + * + * @param string $extension + * @access public + * @return array + */ + public function getPathesFromPackage($extension) + { + $pathes = array(); + $packageFile = $this->getPackageFile($extension); + + /* Get files from the package file. */ + $this->app->loadClass('pclzip', true); + $zip = new pclzip($packageFile); + $files = $zip->listContent(); + if($files) + { + foreach($files as $file) + { + $file = (object)$file; + if($file->folder) continue; + $file->filename = substr($file->filename, strpos($file->filename, '/') + 1); + $pathes[] = dirname($file->filename); + } + } + + /* Append the pathes to stored the extracted files. */ + $pathes[] = "module/extension/ext/"; + + return array_unique($pathes); + } + + /** + * Get all files from a package. + * + * @param string $extension + * @access public + * @return array + */ + public function getFilesFromPackage($extension) + { + $extensionDir = "ext/$extension/"; + $files = $this->classFile->readDir($extensionDir, array('db', 'doc')); + return $files; + } + + /** + * Get the extension's zentaoVersion + * + * @param string $extenstion + * @access public + * @return string + */ + public function getZentaoVersion($extension) + { + $info = $this->parseExtensionCFG($extension); + if(isset($info->zentaoVersion)) return $info->zentaoVersion; + if(isset($info->zentaoversion)) return $info->zentaoversion; + + return ''; + } + + /** + * Process license. If is opensource return the full text of it. + * + * @param string $license + * @access public + * @return string + */ + public function processLicense($license) + { + if(strlen($license) > 10) return $license; // more then 10 letters, not gpl, lgpl, apache, bsd or mit. + + $licenseFile = dirname(__FILE__) . '/license/' . strtolower($license) . '.txt'; + if(file_exists($licenseFile)) return file_get_contents($licenseFile); + + return $license; + } + + /** + * Get hook file for install or uninstall. + * + * @param string $extension + * @param string $hook preinstall|postinstall|preuninstall|postuninstall + * @access public + * @return string|bool + */ + public function getHookFile($extension, $hook) + { + $hookFile = "ext/$extension/hook/$hook.php"; + if(file_exists($hookFile)) return $hookFile; + return false; + } + + /** + * Get the install db file. + * + * @param string $extension + * @param string $method + * @access public + * @return string + */ + public function getDBFile($extension, $method = 'install') + { + return "ext/$extension/db/$method.sql"; + } + + /** + * Check the download path. + * + * @access public + * @return object the check result. + */ + public function checkDownloadPath() + { + /* Init the return. */ + $return->result = 'ok'; + $return->error = ''; + + $tmpRoot = $this->app->getTmpRoot(); + $downloadPath = $tmpRoot . 'extension'; + + if(!is_dir($downloadPath)) + { + if(is_writable($tmpRoot)) + { + mkdir($downloadPath); + } + else + { + $return->result = 'fail'; + $return->error = sprintf($this->lang->extension->errorDownloadPathNotFound, $downloadPath, $downloadPath); + } + } + elseif(!is_writable($downloadPath)) + { + $return->result = 'fail'; + $return->error = sprintf($this->lang->extension->errorDownloadPathNotWritable, $downloadPath, $downloadPath); + } + return $return; + } + + /** + * Check extension files. + * + * @param string $extension + * @access public + * @return object the check result. + */ + public function checkExtensionPathes($extension) + { + $return->result = 'ok'; + $return->errors = ''; + $return->mkdirCommands = ''; + $return->chmodCommands = ''; + $return->dirs2Created = array(); + + $appRoot = $this->app->getAppRoot(); + $pathes = $this->getPathesFromPackage($extension); + foreach($pathes as $path) + { + if($path == 'db' or $path == 'doc' or $path == 'hook') continue; + $path = $appRoot . $path; + if(is_dir($path)) + { + if(!is_writable($path)) + { + $return->errors .= sprintf($this->lang->extension->errorTargetPathNotWritable, $path) . '
        '; + $return->chmodCommands .= "sudo chmod -R 777 $path
        "; + } + } + else + { + if(!@mkdir($path, 0755, true)) + { + $return->errors .= sprintf($this->lang->extension->errorTargetPathNotExists, $path) . '
        '; + $return->mkdirCommands .= "mkdir -p $path
        "; + $return->chmodCommands .= "sudo chmod -R 777 $path
        "; + } + $return->dirs2Created[] = $path; + } + } + + if($return->errors) $return->result = 'fail'; + $return->mkdirCommands = str_replace('/', DIRECTORY_SEPARATOR, $return->mkdirCommands); + $return->errors .= $this->lang->extension->executeCommands . $return->mkdirCommands; + if(PHP_OS == 'Linux') $return->errors .= $return->chmodCommands; + return $return; + } + + /** + * Check the extension's version is compatibility for zentao version + * + * @param string $version + * @access public + * @return bool + */ + public function checkVersion($version) + { + if($version == 'all') return true; + $version = explode(',', $version); + if(in_array($this->config->version, $version)) return true; + return false; + } + + /** + * Check files in the package conflicts with exists files or not. + * + * @param string $extension + * @param string $type + * @param bool $isCheck + * @access public + * @return object + */ + public function checkFile($extension) + { + $return->result = 'ok'; + $return->error = ''; + + $extensionFiles = $this->getFilesFromPackage($extension); + $appRoot = $this->app->getAppRoot(); + foreach($extensionFiles as $extensionFile) + { + $compareFile = $appRoot . str_replace(realpath("ext/$extension") . '/', '', $extensionFile); + if(!file_exists($compareFile)) continue; + if(md5_file($extensionFile) != md5_file($compareFile)) $return->error .= $compareFile . '
        '; + } + + if($return->error != '') $return->result = 'fail'; + return $return; + } + + /** + * Extract an extension. + * + * @param string $extension + * @access public + * @return object + */ + public function extractPackage($extension) + { + $return->result = 'ok'; + $return->error = ''; + + /* try remove pre extracted files. */ + $extensionPath = "ext/$extension"; + if(is_dir($extensionPath)) $this->classFile->removeDir($extensionPath); + + /* Extract files. */ + $packageFile = $this->getPackageFile($extension); + $this->app->loadClass('pclzip', true); + $zip = new pclzip($packageFile); + $files = $zip->listContent(); + $removePath = $files[0]['filename']; + if($zip->extract(PCLZIP_OPT_PATH, $extensionPath, PCLZIP_OPT_REMOVE_PATH, $removePath) == 0) + { + $return->result = 'fail'; + $return->error = $zip->errorInfo(true); + } + + return $return; + } + + /** + * Copy package files. + * + * @param int $extension + * @access public + * @return array + */ + public function copyPackageFiles($extension) + { + $appRoot = $this->app->getAppRoot(); + $extensionDir = "ext/$extension/"; + $pathes = scandir($extensionDir); + $copiedFiles = array(); + + foreach($pathes as $path) + { + if($path == 'db' or $path == 'doc' or $path == 'hook' or $path == '..' or $path == '.') continue; + $copiedFiles = $this->classFile->copyDir($extensionDir . $path, $appRoot . $path); + } + foreach($copiedFiles as $key => $copiedFile) + { + $copiedFiles[$copiedFile] = md5_file($copiedFile); + unset($copiedFiles[$key]); + } + return $copiedFiles; + } + + /** + * Remove an extension. + * + * @param string $extension + * @access public + * @return array the remove commands need executed manually. + */ + public function removePackage($extension) + { + $extension = $this->getInfoFromDB($extension); + if($extension->type == 'patch') return true; + $dirs = json_decode($extension->dirs); + $files = json_decode($extension->files); + $appRoot = $this->app->getAppRoot(); + $removeCommands = array(); + + /* Remove files first. */ + if($files) + { + foreach($files as $file => $savedMD5) + { + $file = $appRoot . $file; + if(!file_exists($file)) continue; + + if(md5_file($file) != $savedMD5) + { + $removeCommands[] = PHP_OS == 'Linux' ? "rm -fr $file #changed" : "del $file :changed"; + } + elseif(!@unlink($file)) + { + $removeCommands[] = PHP_OS == 'Linux' ? "rm -fr $file" : "del $file"; + } + } + } + + /* Then remove dirs. */ + if($dirs) + { + rsort($dirs); // remove from the lower level directory. + foreach($dirs as $dir) + { + if(!@rmdir($appRoot . $dir)) $removeCommands[] = "rmdir $appRoot$dir"; + } + } + + /* Clean model cache files. */ + $this->cleanModelCache(); + + return $removeCommands; + } + + /** + * Clean model cache files. + * + * @access public + * @return void + */ + public function cleanModelCache() + { + $modelCacheFiles = glob($this->app->getTmpRoot() . 'model/*'); + foreach($modelCacheFiles as $cacheFile) @unlink($cacheFile); + } + + /** + * Erase an extension's package file. + * + * @param string $extension + * @access public + * @return array the remove commands need executed manually. + */ + public function erasePackage($extension) + { + $removeCommands = array(); + + $this->dao->delete()->from(TABLE_EXTENSION)->where('code')->eq($extension)->exec(); + + /* Remove the zip file. */ + $packageFile = $this->getPackageFile($extension); + if(file_exists($packageFile) and !@unlink($packageFile)) + { + $removeCommands[] = PHP_OS == 'Linux' ? "rm -fr $packageFile" : "del $packageFile"; + } + + /* Remove the extracted files. */ + $extractedDir = realpath("ext/$extension"); + if($extractedDir != '/' and !$this->classFile->removeDir($extractedDir)) + { + $removeCommands[] = PHP_OS == 'Linux' ? "rm -fr $extractedDir" : "rmdir $extractedDir /s"; + } + + return $removeCommands; + } + + /** + * Judge need execute db install or not. + * + * @param string $extension + * @param string $method + * @access public + * @return bool + */ + public function needExecuteDB($extension, $method = 'install') + { + return file_exists($this->getDBFile($extension, $method)); + } + + /** + * Install the db. + * + * @param int $extension + * @access public + * @return object + */ + public function executeDB($extension, $method = 'install') + { + $return->result = 'ok'; + $return->error = ''; + + $dbFile = $this->getDBFile($extension, $method); + if(!file_exists($dbFile)) return $return; + + $sqls = file_get_contents($this->getDBFile($extension, $method)); + $sqls = explode(';', $sqls); + + foreach($sqls as $sql) + { + $sql = trim($sql); + if(empty($sql)) continue; + $sql = str_replace('zt_', $this->config->db->prefix, $sql); + + try + { + $this->dbh->query($sql); + } + catch (PDOException $e) + { + $return->error .= '

        ' . $e->getMessage() . "
        THE SQL IS: $sql

        "; + } + } + if($return->error) $return->result = 'fail'; + return $return; + } + + /** + * Save the extension to database. + * + * @param string $extension the extension code + * @param string $type the extension type + * @access public + * @return void + */ + public function saveExtension($extension, $type) + { + $code = $extension; + $extension = $this->getInfoFromPackage($extension); + $extension->status = 'available'; + $extension->code = $code; + $extension->type = $type; + $this->dao->replace(TABLE_EXTENSION)->data($extension)->exec(); + } + + /** + * Update an extension. + * + * @param string $extension + * @param string $status + * @param array $files + * @access public + * @return void + */ + public function updateExtension($extension, $data) + { + $data = (object)$data; + $appRoot = $this->app->getAppRoot(); + + if(isset($data->dirs)) + { + if($data->dirs) + { + foreach($data->dirs as $key => $dir) + { + $data->dirs[$key] = str_replace($appRoot, '', $dir); + } + } + $data->dirs = json_encode($data->dirs); + } + + if(isset($data->files)) + { + foreach($data->files as $fullFilePath => $md5) + { + $relativeFilePath = str_replace($appRoot, '', $fullFilePath); + $data->files[$relativeFilePath] = $md5; + unset($data->files[$fullFilePath]); + } + $data->files = json_encode($data->files); + } + return $this->dao->update(TABLE_EXTENSION)->data($data)->where('code')->eq($extension)->exec(); + } +} diff --git a/trunk/module/extension/view/activate.html.php b/trunk/module/extension/view/activate.html.php new file mode 100644 index 0000000000..2e2335c3ba --- /dev/null +++ b/trunk/module/extension/view/activate.html.php @@ -0,0 +1,33 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        title;?>
        + {$header->title}"; + echo "

        " . html::commonButton($lang->extension->viewInstalled, 'onclick=parent.location.href="' . inlink('browse', 'type=installed') . '"') . '

        '; + } + ?> +
        + + diff --git a/trunk/module/extension/view/browse.html.php b/trunk/module/extension/view/browse.html.php new file mode 100644 index 0000000000..63e12ce559 --- /dev/null +++ b/trunk/module/extension/view/browse.html.php @@ -0,0 +1,61 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
        name";?>
        +
        desc;?>
        +
        + extension->version}: {$extension->version} "; + echo "{$lang->extension->author}: {$extension->author} "; + ?> +
        +
        + code"), $lang->extension->structure, '', "class='button-c iframe'"); + $deactivateCode = html::a(inlink('deactivate', "extension=$extension->code"), $lang->extension->deactivate, '', "class='button-c iframe'"); + $activateCode = html::a(inlink('activate', "extension=$extension->code"), $lang->extension->activate, '', "class='button-c iframe'"); + $uninstallCode = html::a(inlink('uninstall', "extension=$extension->code"), $lang->extension->uninstall, '', "class='button-c iframe'"); + $installCode = html::a(inlink('install', "extension=$extension->code"), $lang->extension->install, '', "class='button-c iframe'"); + $eraseCode = html::a(inlink('erase', "extension=$extension->code"), $lang->extension->erase, '', "class='button-c iframe'"); + + if(isset($extension->viewLink)) + { + echo html::a($extension->viewLink, $lang->extension->view, '', "class='button-c extension'"); + } + if($extension->status == 'installed') + { + echo $structureCode; + } + if($extension->status == 'installed' and !empty($extension->upgradeLink)) + { + echo html::a($extension->upgradeLink, $lang->extension->upgrade, '', "class='button-c iframe'"); + } + + if($extension->type != 'patch') + { + if($extension->status == 'installed') echo $deactivateCode . $uninstallCode; + if($extension->status == 'deactivated') echo $activateCode . $uninstallCode; + if($extension->status == 'available') echo $installCode . $eraseCode; + } + echo html::a($extension->site, $lang->extension->site, '_blank', 'class=button-c'); + ?> +
        + + diff --git a/trunk/module/extension/view/checkscore.html.php b/trunk/module/extension/view/checkscore.html.php new file mode 100644 index 0000000000..aed10a40ec --- /dev/null +++ b/trunk/module/extension/view/checkscore.html.php @@ -0,0 +1,28 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        title;?>
        + + {$lang->extension->needSorce}"; + echo "

        $error

        "; + ?> + +
        + + diff --git a/trunk/module/extension/view/deactivate.html.php b/trunk/module/extension/view/deactivate.html.php new file mode 100644 index 0000000000..800fb2a4d8 --- /dev/null +++ b/trunk/module/extension/view/deactivate.html.php @@ -0,0 +1,31 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        title;?>
        + {$header->title}"; + if($removeCommands) + { + echo "

        {$lang->extension->unremovedFiles}

        "; + echo join($removeCommands, '
        '); + } + echo "

        " . html::commonButton($lang->extension->viewDeactivated, 'onclick=parent.location.href="' . inlink('browse', 'type=deactivated') . '"') . '

        '; + ?> +
        + + diff --git a/trunk/module/extension/view/erase.html.php b/trunk/module/extension/view/erase.html.php new file mode 100644 index 0000000000..91994e1766 --- /dev/null +++ b/trunk/module/extension/view/erase.html.php @@ -0,0 +1,31 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        title;?>
        + {$header->title}"; + if($removeCommands) + { + echo "

        {$lang->extension->unremovedFiles}

        "; + echo join($removeCommands, '
        '); + } + echo "

        " . html::commonButton($lang->extension->viewAvailable, 'onclick=parent.location.href="' . inlink('browse', 'type=available') . '"') . '

        '; + ?> +
        + + diff --git a/trunk/module/extension/view/header.html.php b/trunk/module/extension/view/header.html.php new file mode 100644 index 0000000000..7221a5ad8b --- /dev/null +++ b/trunk/module/extension/view/header.html.php @@ -0,0 +1,14 @@ + + +
        +
        + ' . html::a(inlink('browse', "type=installed"), $lang->extension->installed) . ''; + echo ''. html::a(inlink('browse', "type=deactivated"), $lang->extension->deactivated) . ''; + echo '' . html::a(inlink('browse', "type=available"), $lang->extension->available ) . ''; + echo ' ' . html::a(inlink('obtain'), $lang->extension->obtain) . ''; + if(common::hasPriv('extension', 'upload')) echo '' . html::a(inlink('upload'), $lang->extension->upload, '', "class='iframe'") . ''; + ?> +
        + +
        diff --git a/trunk/module/extension/view/install.html.php b/trunk/module/extension/view/install.html.php new file mode 100644 index 0000000000..6be0901b48 --- /dev/null +++ b/trunk/module/extension/view/install.html.php @@ -0,0 +1,54 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        +title; +if(isset($license) and $upgrade == 'yes') printf($lang->extension->upgradeVersion, $this->post->installedVersion, $this->post->upgradeVersion); +?> +
        + + " . sprintf($lang->extension->installFailed, $installType) . ""; + echo "

        $error

        "; + echo html::commonButton($lang->extension->refreshPage, 'onclick=location.href=location.href'); + ?> + + {$lang->extension->license}"; + echo html::textarea('license', $license) . '
        '; + echo '

        ' . html::a($agreeLink, $lang->extension->agreeLicense) . '

        '; + ?> + + {$lang->extension->successDownloadedPackage}"; + echo "

        {$lang->extension->successCopiedFiles}

        "; + echo '
          '; + foreach($files as $fileName => $md5) + { + echo "
        • $fileName
        • "; + } + echo '
        '; + echo "

        {$lang->extension->successInstallDB}

        "; + echo "

        " . sprintf($lang->extension->installFinished, $installType) . "

        "; + echo "

        " . html::commonButton($lang->extension->viewInstalled, 'onclick=parent.location.href="' . inlink('browse') . '"') . '

        '; + ?> + +
        + + diff --git a/trunk/module/extension/view/obtain.html.php b/trunk/module/extension/view/obtain.html.php new file mode 100644 index 0000000000..963624a332 --- /dev/null +++ b/trunk/module/extension/view/obtain.html.php @@ -0,0 +1,113 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
        +
        extension->obtain;?>
        +
        + " . html::a(inlink('obtain', 'type=byUpdatedTime'), $lang->extension->byUpdatedTime) . '
        '; + echo "" . html::a(inlink('obtain', 'type=byAddedTime'), $lang->extension->byAddedTime) . '
        '; + echo "" . html::a(inlink('obtain', 'type=byDownloads'), $lang->extension->byDownloads) . '
        '; + ?> +
        +
        extension->bySearch;?>
        +
        +
        '> + post->key, "class='text-1'") . html::submitButton($lang->extension->bySearch);?> +
        +
        +
        extension->byCategory;?>
        +
        + extension->errorGetModules);?> +
        +
        + + + currentRelease; + $latestRelease = $extension->latestRelease; + ?> + + + + + +
        +
        name . "($currentRelease->releaseVersion)";?>
        +
        + releaseVersion != $currentRelease->releaseVersion) + { + printf($lang->extension->latest, $latestRelease->viewLink, $latestRelease->releaseVersion, $latestRelease->zentaoVersion); + }?> +
        +
        +
        abstract;?>
        +
        + extension->author}: {$extension->author} "; + echo "{$lang->extension->downloads}: {$extension->downloads} "; + echo "{$lang->extension->compatible}: {$lang->extension->compatibleList[$currentRelease->compatible]} "; + echo "{$lang->extension->grade}: ", html::printStars($extension->stars); + ?> +
        +
        + code&downLink=" . helper::safe64Encode($currentRelease->downLink) . "&md5={$currentRelease->md5}&type=$extension->type&&overridePackage=no&ignoreCompitable=yes"); + echo html::a($extension->viewLink, $lang->extension->view, '', 'class="button-c extension"'); + if($currentRelease->public) + { + if($extension->type != 'computer' and $extension->type != 'mobile') + { + if(isset($installeds[$extension->code])) + { + if($installeds[$extension->code]->version != $extension->latestRelease->releaseVersion and $this->extension->checkVersion($extension->latestRelease->zentaoVersion)) + { + $upgradeLink = inlink('upgrade', "extension=$extension->code&downLink=" . helper::safe64Encode($currentRelease->downLink) . "&md5=$currentRelease->md5&type=$extension->type"); + echo html::a($upgradeLink, $lang->extension->upgrade, '', 'class="iframe button-c"'); + } + else + { + echo html::commonButton($lang->extension->installed, "disabled='disabled' style='color:gray'"); + } + } + else + { + $label = $currentRelease->compatible ? $lang->extension->installAuto : $lang->extension->installForce; + echo html::a($installLink, $label, '', 'class="iframe button-c"'); + } + } + } + echo html::a($currentRelease->downLink, $lang->extension->downloadAB, '', 'class="manual button-c"'); + echo html::a($extension->site, $lang->extension->site, '_blank', 'class=button-c'); + ?> +
        + + show();?> + +
        extension->errorOccurs;?>
        +
        extension->errorGetExtensions;?>
        + +
        + + diff --git a/trunk/module/extension/view/structure.html.php b/trunk/module/extension/view/structure.html.php new file mode 100644 index 0000000000..c0fdcf230f --- /dev/null +++ b/trunk/module/extension/view/structure.html.php @@ -0,0 +1,26 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + +
        name . '[' . $extension->code . '] ' .$lang->extension->structure . ':';?> +
        + app->getAppRoot(); + $files = json_decode($extension->files); + foreach($files as $file => $md5) echo $appRoot . $file . "
        "; + ?> +
        + diff --git a/trunk/module/extension/view/uninstall.html.php b/trunk/module/extension/view/uninstall.html.php new file mode 100644 index 0000000000..e8eff9d9d6 --- /dev/null +++ b/trunk/module/extension/view/uninstall.html.php @@ -0,0 +1,31 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        title;?>
        + {$header->title}"; + if($removeCommands) + { + echo "

        {$lang->extension->unremovedFiles}

        "; + echo join($removeCommands, '
        '); + } + echo "

        " . html::commonButton($lang->extension->viewAvailable, 'onclick=parent.location.href="' . inlink('browse', 'type=available') . '"') . '

        '; + ?> +
        + + diff --git a/trunk/module/extension/view/upload.html.php b/trunk/module/extension/view/upload.html.php new file mode 100644 index 0000000000..56eb4470d8 --- /dev/null +++ b/trunk/module/extension/view/upload.html.php @@ -0,0 +1,19 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
        + + extension->install);?> +
        + + diff --git a/trunk/module/extension/view/waring.htm.php b/trunk/module/extension/view/waring.htm.php new file mode 100644 index 0000000000..866107d546 --- /dev/null +++ b/trunk/module/extension/view/waring.htm.php @@ -0,0 +1,29 @@ + + * @package extension + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
        title;?>
        + + {$lang->extension->waringInstall}"; + echo "

        $error

        "; + echo html::commonButton($lang->extension->refreshPage, 'onclick=location.href=location.href'); + ?> + +
        + + diff --git a/trunk/module/file/config.php b/trunk/module/file/config.php new file mode 100644 index 0000000000..55333f611e --- /dev/null +++ b/trunk/module/file/config.php @@ -0,0 +1,5 @@ +file->mimes['xml'] = 'text/xml'; +$config->file->mimes['html'] = 'text/html'; +$config->file->mimes['csv'] = 'text/csv'; +$config->file->mimes['default'] = 'application/octet-stream'; diff --git a/trunk/module/file/control.php b/trunk/module/file/control.php new file mode 100644 index 0000000000..999f0f6bb6 --- /dev/null +++ b/trunk/module/file/control.php @@ -0,0 +1,294 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +class file extends control +{ + /** + * Build the upload form. + * + * @param int $fileCount + * @param float $percent + * @access public + * @return void + */ + public function buildForm($fileCount = 2, $percent = 0.9) + { + if(!file_exists($this->file->savePath)) + { + printf($this->lang->file->errorNotExists, $this->file->savePath); + return false; + } + elseif(!is_writable($this->file->savePath)) + { + printf($this->lang->file->errorCanNotWrite, $this->file->savePath, $this->file->savePath); + return false; + } + $this->view->fileCount = $fileCount; + $this->view->percent = $percent; + $this->display(); + } + + /** + * AJAX: get upload request from the web editor. + * + * @access public + * @return void + */ + public function ajaxUpload() + { + $file = $this->file->getUpload('imgFile'); + $file = $file[0]; + if($file) + { + if(@move_uploaded_file($file['tmpname'], $this->file->savePath . $file['pathname'])) + { + $url = $this->file->webPath . $file['pathname']; + + $file['addedBy'] = $this->app->user->account; + $file['addedDate'] = helper::today(); + unset($file['tmpname']); + $this->dao->insert(TABLE_FILE)->data($file)->exec(); + + die(json_encode(array('error' => 0, 'url' => $url))); + } + else + { + $error = strip_tags(sprintf($this->lang->file->errorCanNotWrite, $this->file->savePath, $this->file->savePath)); + die(json_encode(array('error' => 1, 'message' => $error))); + } + } + } + + /** + * Down a file. + * + * @param int $fileID + * @param string $mouse + * @access public + * @return void + */ + public function download($fileID, $mouse = '') + { + $file = $this->file->getById($fileID); + + /* Judge the mode, down or open. */ + $mode = 'down'; + $fileTypes = 'txt|jpg|jpeg|gif|png|bmp|xml|html'; + if(stripos($fileTypes, $file->extension) !== false and $mouse == 'left') $mode = 'open'; + + /* If the mode is open, locate directly. */ + if($mode == 'open') + { + if(file_exists($file->realPath))$this->locate($file->webPath); + $this->app->error("The file you visit $fileID not found.", __FILE__, __LINE__, true); + } + else + { + /* Down the file. */ + if(file_exists($file->realPath)) + { + $fileName = $file->title . '.' . $file->extension; + $fileData = file_get_contents($file->realPath); + $this->sendDownHeader($fileName, $file->extension, $fileData); + } + else + { + $this->app->error("The file you visit $fileID not found.", __FILE__, __LINE__, true); + } + } + } + + /** + * Export as csv format. + * + * @access public + * @return void + */ + public function export2CSV() + { + $this->view->fields = $this->post->fields; + $this->view->rows = $this->post->rows; + $output = $this->parse('file', 'export2csv'); + if( $this->post->encode != "utf-8") + { + if(function_exists('mb_convert_encoding')) + { + $output = @mb_convert_encoding($output, $this->post->encode, 'utf-8'); + } + elseif(function_exists('iconv')) + { + $output = @iconv('utf-8', $this->post->encode, $output); + } + } + + $this->sendDownHeader($this->post->fileName, 'csv', $output); + } + + /** + * export as xml format + * + * @access public + * @return void + */ + public function export2XML() + { + $this->view->fields = $this->post->fields; + $this->view->rows = $this->post->rows; + + $output = $this->parse('file', 'export2XML'); + + $this->sendDownHeader($this->post->fileName, 'xml', $output); + } + + /** + * export as html format + * + * @access public + * @return void + */ + public function export2HTML() + { + $this->view->fields = $this->post->fields; + $this->view->rows = $this->post->rows; + $this->host = common::getSysURL(); + + switch($this->post->kind) + { + case 'task': + foreach($this->view->rows as $row) + { + $row->name = html::a($this->host . $this->createLink('task', 'view', "taskID=$row->id"), $row->name); + } + break; + case 'story': + foreach($this->view->rows as $row) + { + $row->title= html::a($this->host . $this->createLink('story', 'view', "storyID=$row->id"), $row->title); + } + break; + case 'bug': + foreach($this->view->rows as $row) + { + $row->title= html::a($this->host . $this->createLink('bug', 'view', "bugID=$row->id"), $row->title); + } + break; + case 'testcase': + foreach($this->view->rows as $row) + { + $row->title= html::a($this->host . $this->createLink('testcase', 'view', "caseID=$row->id"), $row->title); + } + break; + } + $this->view->fileName = $this->post->fileName; + $output = $this->parse('file', 'export2Html'); + + $this->sendDownHeader($this->post->fileName, 'html', $output); + } + + /** + * Send the download header to the client. + * + * @param string $fileName + * @param string $extension + * @access public + * @return void + */ + public function sendDownHeader($fileName, $fileType, $content) + { + ob_clean(); // clean the ob content to make sure no space or utf-8 bom output. + + /* Set the downloading cookie, thus the export form page can use it to judge whether to close the window or not. */ + setcookie('downloading', 1); + + /* Append the extension name auto. */ + $extension = '.' . $fileType; + if(strpos($fileName, $extension) === false) $fileName .= $extension; + + /* urlencode the filename for ie. */ + if(strpos($this->server->http_user_agent, 'MSIE') !== false) $fileName = urlencode($fileName); + + /* Judge the content type. */ + $mimes = $this->config->file->mimes; + $contentType = isset($mimes[$fileType]) ? $mimes[$fileType] : $mimes['default']; + + header("Content-type: $contentType"); + header("Content-Disposition: attachment; filename=\"$fileName\""); + header("Pragma: no-cache"); + header("Expires: 0"); + die($content); + } + + /** + * Delete a file. + * + * @param int $fileID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($fileID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->file->confirmDelete, inlink('delete', "fileID=$fileID&confirm=yes"))); + } + else + { + $file = $this->file->getById($fileID); + $this->dao->delete()->from(TABLE_FILE)->where('id')->eq($fileID)->exec(); + $this->loadModel('action')->create($file->objectType, $file->objectID, 'deletedFile', '', $extra=$file->title); + @unlink($file->realPath); + die(js::reload('parent')); + } + } + + /** + * Print files. + * + * @param array $files + * @param string $fieldset + * @access public + * @return void + */ + public function printFiles($files, $fieldset) + { + $this->view->files = $files; + $this->view->fieldset = $fieldset; + $this->display(); + } + + /** + * Edit file's name. + * + * @param int $fileID + * @access public + * @return void + */ + public function edit($fileID) + { + if($_POST) + { + $this->app->loadLang('action'); + $file = $this->file->getByID($fileID); + $this->dao->update(TABLE_FILE)->set('title')->eq($this->post->fileName)->where('id')->eq($fileID)->exec(); + + $extension = "." . $file->extension; + $actionID = $this->loadModel('action')->create($file->objectType, $file->objectID, 'editfile', '', $this->post->fileName . $extension); + $changes[] = array('field' => 'fileName', 'old' => $file->title . $extension, 'new' => $this->post->fileName . $extension); + $this->action->logHistory($actionID, $changes); + + die(js::reload('parent.parent')); + } + + $this->view->file = $this->file->getById($fileID); + $this->display(); + } +} diff --git a/trunk/module/file/lang/en.php b/trunk/module/file/lang/en.php new file mode 100644 index 0000000000..02ad006c14 --- /dev/null +++ b/trunk/module/file/lang/en.php @@ -0,0 +1,23 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->file->common = 'File'; +$lang->file->download = 'Download'; +$lang->file->edit = 'Edit file name'; +$lang->file->inputFileName = 'Input file name'; +$lang->file->delete = 'Delete'; +$lang->file->export2CSV = 'Export CSV'; +$lang->file->ajaxUpload = 'AJAX: upload file from editor'; +$lang->file->label = 'Title: '; + +$lang->file->errorNotExists = "The directory '%s' is no exist"; +$lang->file->errorCanNotWrite = "The directory '%s' is unwritable, please change it's permission. Command in linux:sudo -R chmod 777 '%s'"; +$lang->file->confirmDelete = " Are you sure to delete this file?"; diff --git a/trunk/module/file/lang/zh-cn.php b/trunk/module/file/lang/zh-cn.php new file mode 100644 index 0000000000..f74c0421e8 --- /dev/null +++ b/trunk/module/file/lang/zh-cn.php @@ -0,0 +1,23 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->file->common = '附件'; +$lang->file->download = '下载附件'; +$lang->file->edit = '编辑附件名称'; +$lang->file->inputFileName = '请输入附件名称'; +$lang->file->delete = '删除附件'; +$lang->file->export2CSV = '导出CSV'; +$lang->file->ajaxUpload = '接口:编辑器上传附件'; +$lang->file->label = '标题:'; + +$lang->file->errorNotExists = "文件夹 '%s' 不存在"; +$lang->file->errorCanNotWrite = "文件夹 '%s' 不可写,请改变文件夹的权限。在linux中输入指令:sudo chmod -R 777 '%s'"; +$lang->file->confirmDelete = " 您确定删除该附件吗?"; diff --git a/trunk/module/file/lang/zh-tw.php b/trunk/module/file/lang/zh-tw.php new file mode 100644 index 0000000000..9993f98a9f --- /dev/null +++ b/trunk/module/file/lang/zh-tw.php @@ -0,0 +1,23 @@ + + * @package file + * @version $Id: zh-tw.php 2605 2012-02-21 07:22:58Z wwccss $ + * @link http://www.zentao.net + */ +$lang->file->common = '附件'; +$lang->file->download = '下載附件'; +$lang->file->edit = '編輯附件名稱'; +$lang->file->inputFileName = '請輸入附件名稱'; +$lang->file->delete = '刪除附件'; +$lang->file->export2CSV = '導出CSV'; +$lang->file->ajaxUpload = '介面:編輯器上傳附件'; +$lang->file->label = '標題:'; + +$lang->file->errorNotExists = "檔案夾 '%s' 不存在"; +$lang->file->errorCanNotWrite = "檔案夾 '%s' 不可寫,請改變檔案夾的權限。在linux中輸入指令:sudo chmod -R 777 '%s'"; +$lang->file->confirmDelete = " 您確定刪除該附件嗎?"; diff --git a/trunk/module/file/model.php b/trunk/module/file/model.php new file mode 100644 index 0000000000..82288b8846 --- /dev/null +++ b/trunk/module/file/model.php @@ -0,0 +1,209 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> +now = time(); + $this->setSavePath(); + $this->setWebPath(); + } + + /** + * Get files of an object. + * + * @param string $objectType + * @param string $objectID + * @access public + * @return array + */ + public function getByObject($objectType, $objectID) + { + return $this->dao->select('*')->from(TABLE_FILE)->where('objectType')->eq($objectType)->andWhere('objectID')->eq((int)$objectID)->orderBy('id')->fetchAll(); + } + + /** + * Get info of a file. + * + * @param int $fileID + * @access public + * @return object + */ + public function getById($fileID) + { + $file = $this->dao->findById($fileID)->from(TABLE_FILE)->fetch(); + $file->webPath = $this->webPath . $file->pathname; + $file->realPath = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . $file->pathname; + return $file; + } + + /** + * Save upload. + * + * @param string $objectType + * @param string $objectID + * @param string $extra + * @access public + * @return array + */ + public function saveUpload($objectType = '', $objectID = '', $extra = '') + { + $fileTitles = array(); + $now = helper::today(); + $files = $this->getUpload(); + + foreach($files as $id => $file) + { + move_uploaded_file($file['tmpname'], $this->savePath . $file['pathname']); + $file['objectType'] = $objectType; + $file['objectID'] = $objectID; + $file['addedBy'] = $this->app->user->account; + $file['addedDate'] = $now; + $file['extra'] = $extra; + unset($file['tmpname']); + $this->dao->insert(TABLE_FILE)->data($file)->exec(); + $fileTitles[$this->dao->lastInsertId()] = $file['title']; + } + return $fileTitles; + } + + /** + * Get counts of uploaded files. + * + * @access public + * @return int + */ + public function getCount() + { + return count($this->getUpload()); + } + + /** + * Get info of uploaded files. + * + * @param string $htmlTagName + * @access public + * @return array + */ + public function getUpload($htmlTagName = 'files') + { + $files = array(); + if(!isset($_FILES[$htmlTagName])) return $files; + + /* If the file var name is an array. */ + if(is_array($_FILES[$htmlTagName]['name'])) + { + extract($_FILES[$htmlTagName]); + foreach($name as $id => $filename) + { + if(empty($filename)) continue; + $file['extension'] = $this->getExtension($filename); + $file['pathname'] = $this->setPathName($id, $file['extension']); + $file['title'] = !empty($_POST['labels'][$id]) ? htmlspecialchars($_POST['labels'][$id]) : str_replace('.' . $file['extension'], '', $filename); + $file['size'] = $size[$id]; + $file['tmpname'] = $tmp_name[$id]; + $files[] = $file; + } + } + else + { + if(empty($_FILES[$htmlTagName]['name'])) return $files; + extract($_FILES[$htmlTagName]); + $file['extension'] = $this->getExtension($name); + $file['pathname'] = $this->setPathName(0, $file['extension']); + $file['title'] = !empty($_POST['labels'][0]) ? htmlspecialchars($_POST['labels'][0]) : substr($name, 0, strpos($name, $file['extension']) - 1); + $file['size'] = $size; + $file['tmpname'] = $tmp_name; + return array($file); + } + return $files; + } + + /** + * Get extension of a file. + * + * @param string $filename + * @access public + * @return string + */ + public function getExtension($filename) + { + $extension = pathinfo($filename, PATHINFO_EXTENSION); + if(strpos($this->config->file->dangers, $extension) !== false) return 'txt'; + return $extension; + } + + /** + * Set path name of the uploaded file to be saved. + * + * @param int $fileID + * @param string $extension + * @access public + * @return string + */ + public function setPathName($fileID, $extension) + { + $sessionID = session_id(); + $randString = substr($sessionID, mt_rand(0, strlen($sessionID) - 5), 3); + return date('Ym/dHis', $this->now) . $fileID . mt_rand(0, 10000) . $randString . '.' . $extension; + } + + /** + * Set save path. + * + * @access public + * @return void + */ + public function setSavePath() + { + $savePath = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . date('Ym/', $this->now); + if(!file_exists($savePath)) @mkdir($savePath, 0777, true); + $this->savePath = dirname($savePath) . '/'; + } + + /** + * Set the web path of upload files. + * + * @access public + * @return void + */ + public function setWebPath() + { + $this->webPath = $this->app->getWebRoot() . "data/upload/{$this->app->company->id}/"; + } + + /** + * Insert the set image size code. + * + * @param string $content + * @param int $maxSize + * @access public + * @return string + */ + public function setImgSize($content, $maxSize = 0) + { + return str_replace('src="data/upload', 'onload="setImageSize(this,' . $maxSize . ' )" src="data/upload', $content); + } +} diff --git a/trunk/module/file/view/buildform.html.php b/trunk/module/file/view/buildform.html.php new file mode 100644 index 0000000000..b71e9b2707 --- /dev/null +++ b/trunk/module/file/view/buildform.html.php @@ -0,0 +1,78 @@ +
        + + {$lang->file->common}\$i + + + + + +
        +EOT; + for($i = 1; $i <= $fileCount; $i ++) echo str_replace('$i', $i, $fileRow); +?> +
      + diff --git a/trunk/module/file/view/edit.html.php b/trunk/module/file/view/edit.html.php new file mode 100644 index 0000000000..b3490e46f3 --- /dev/null +++ b/trunk/module/file/view/edit.html.php @@ -0,0 +1,41 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + +
      + + + + + +
      file->inputFileName;?>
      + title) . ".{$file->extension}";?> + +
      +
      + diff --git a/trunk/module/file/view/export.html.php b/trunk/module/file/view/export.html.php new file mode 100644 index 0000000000..86147489ac --- /dev/null +++ b/trunk/module/file/view/export.html.php @@ -0,0 +1,62 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +

      +
      + + + + + +
      export;?>
      + setFileName;?> + + exportFileTypeList, '', 'onchange=switchEncode(this.value)');?> + exportFileTypeList) == 'csv' ? html::select('encode', $lang->exportEncodeList, 'gbk') : '';?> + +
      +
      + diff --git a/trunk/module/file/view/export2csv.html.php b/trunk/module/file/view/export2csv.html.php new file mode 100644 index 0000000000..1e8f1b53ca --- /dev/null +++ b/trunk/module/file/view/export2csv.html.php @@ -0,0 +1,24 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + $fieldLabel) + { + isset($row->$fieldName) ? print(strip_tags($row->$fieldName)) : print(''); + echo '","'; + } + echo '"' . "\n"; +} diff --git a/trunk/module/file/view/export2html.html.php b/trunk/module/file/view/export2html.html.php new file mode 100644 index 0000000000..3fbb16b749 --- /dev/null +++ b/trunk/module/file/view/export2html.html.php @@ -0,0 +1,44 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + +<?php echo $fileName;?> + + + + $fieldLabel\n"; + } + ?> + +\n"; + foreach($fields as $fieldName => $fieldLabel) + { + $fieldValue = isset($row->$fieldName) ? $row->$fieldName : ''; + echo "\n"; + } + echo "\n"; +} +?> +
      $fieldValue
      + + diff --git a/trunk/module/file/view/export2xml.html.php b/trunk/module/file/view/export2xml.html.php new file mode 100644 index 0000000000..3747ab98b7 --- /dev/null +++ b/trunk/module/file/view/export2xml.html.php @@ -0,0 +1,36 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> +\n";?> + + $fieldLabel) +{ + echo " <$fieldName>$fieldLabel\n"; +} +?> + + +\n"; + foreach($fields as $fieldName => $fieldLabel) + { + $fieldValue = isset($row->$fieldName) ? htmlspecialchars($row->$fieldName) : ''; + echo " <$fieldName>$fieldValue\n"; + } + echo " \n"; +} +?> + + diff --git a/trunk/module/file/view/printfiles.html.php b/trunk/module/file/view/printfiles.html.php new file mode 100644 index 0000000000..21ea5fcf9a --- /dev/null +++ b/trunk/module/file/view/printfiles.html.php @@ -0,0 +1,41 @@ +requestType == 'PATH_INFO' ? '?' : '&'; +$sessionString .= session_name() . '=' . session_id(); +?> + + + +
      + file->common;?> + +
      + createLink('file', 'download', "fileID=$file->id") . $sessionString, $file->title .'.' . $file->extension, '_blank', "onclick='return downloadFile($file->id)'"); + if(common::hasPriv('file', 'edit')) echo html::a($this->createLink('file', 'edit', "fileID=$file->id"), $lang->edit, '', "class='edit'"); + if(common::hasPriv('file', 'delete')) echo html::commonButton(' x ', "onclick='deleteFile($file->id)'"); + } + ?> +
      +';?> diff --git a/trunk/module/group/config.php b/trunk/module/group/config.php new file mode 100644 index 0000000000..dc99108347 --- /dev/null +++ b/trunk/module/group/config.php @@ -0,0 +1,3 @@ +group->create->requiredFields = 'name'; +$config->group->edit->requiredFields = 'name'; diff --git a/trunk/module/group/control.php b/trunk/module/group/control.php new file mode 100644 index 0000000000..670ee92d76 --- /dev/null +++ b/trunk/module/group/control.php @@ -0,0 +1,225 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +class group extends control +{ + /** + * Construct function. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('company')->setMenu(); + $this->loadModel('user'); + } + + /** + * Browse groups. + * + * @param int $companyID + * @access public + * @return void + */ + public function browse($companyID = 0) + { + if($companyID == 0) $companyID = $this->app->company->id; + + $header['title'] = $this->lang->company->orgView . $this->lang->colon . $this->lang->group->browse; + $position[] = $this->lang->group->browse; + + $groups = $this->group->getList($companyID); + $groupUsers = array(); + foreach($groups as $group) $groupUsers[$group->id] = $this->group->getUserPairs($group->id); + + $this->view->header = $header; + $this->view->position = $position; + $this->view->groups = $groups; + $this->view->groupUsers = $groupUsers; + + $this->display(); + } + + /** + * Create a group. + * + * @access public + * @return void + */ + public function create() + { + if(!empty($_POST)) + { + $this->group->create(); + if(dao::isError()) die(js::error(dao::getError())); + die(js::locate($this->createLink('group', 'browse'), 'parent')); + } + + $this->view->header->title = $this->lang->company->orgView . $this->lang->colon . $this->lang->group->create; + $this->view->position[] = $this->lang->group->create; + $this->display(); + } + + /** + * Edit a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function edit($groupID) + { + if(!empty($_POST)) + { + $this->group->update($groupID); + die(js::locate($this->createLink('group', 'browse'), 'parent')); + } + + $header['title'] = $this->lang->company->orgView . $this->lang->colon . $this->lang->group->edit; + $position[] = $this->lang->group->edit; + $this->view->header = $header; + $this->view->position = $position; + $this->view->group = $this->group->getById($groupID); + + $this->display(); + } + + /** + * Copy a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function copy($groupID) + { + if(!empty($_POST)) + { + $this->group->copy($groupID); + if(dao::isError()) die(js::error(dao::getError())); + die(js::locate($this->createLink('group', 'browse'), 'parent')); + } + + $this->view->header->title = $this->lang->company->orgView . $this->lang->colon . $this->lang->group->copy; + $this->view->position[] = $this->lang->group->copy; + $this->view->group = $this->group->getById($groupID); + $this->display(); + } + + /** + * Manage privleges of a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function managePriv($type = 'byGroup', $param = 0) + { + if($type == 'byGroup') $groupID = $param; + $this->view->type = $type; + foreach($this->lang->resource as $moduleName => $action) $this->app->loadLang($moduleName); + + if(!empty($_POST)) + { + if($type == 'byGroup') $result = $this->group->updatePrivByGroup($groupID); + if($type == 'byModule') $result = $this->group->updatePrivByModule(); + print(js::alert($result ? $this->lang->group->successSaved : $this->lang->group->errorNotSaved)); + exit; + } + + if($type == 'byGroup') + { + $this->group->sortResource(); + $group = $this->group->getById($groupID); + $groupPrivs = $this->group->getPrivs($groupID); + + $this->view->header->title = $this->lang->company->common . $this->lang->colon . $group->name . $this->lang->colon . $this->lang->group->managePriv; + $this->view->position[] = $group->name . $this->lang->colon . $this->lang->group->managePriv; + + $this->view->group = $group; + $this->view->changelogs = $this->lang->changelog; + $this->view->groupPrivs = $groupPrivs; + } + elseif($type == 'byModule') + { + $this->group->sortResource(); + $this->view->header->title = $this->lang->company->common . $this->lang->colon . $this->lang->group->managePriv; + $this->view->position[] = $this->lang->group->managePriv; + + foreach($this->lang->resource as $module => $moduleActions) + { + $modules[$module] = $this->lang->$module->common; + foreach($moduleActions as $action) + { + $actions[$module][$action] = $this->lang->$module->$action; + } + } + $this->view->groups = $this->group->getPairs(); + $this->view->modules = $modules; + $this->view->actions = $actions; + } + $this->display(); + } + + /** + * Manage members of a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function manageMember($groupID) + { + if(!empty($_POST)) + { + $this->group->updateUser($groupID); + die(js::locate($this->createLink('group', 'browse'), 'parent')); + } + $group = $this->group->getById($groupID); + $groupUsers = $this->group->getUserPairs($groupID); + $allUsers = $this->user->getPairs('nodeleted|noclosed|noempty|noletter'); + $otherUsers = array_diff_assoc($allUsers, $groupUsers); + + $header['title'] = $this->lang->company->common . $this->lang->colon . $group->name . $this->lang->colon . $this->lang->group->manageMember; + $position[] = $group->name . $this->lang->colon . $this->lang->group->manageMember; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->group = $group; + $this->view->groupUsers = $groupUsers; + $this->view->otherUsers = $otherUsers; + + $this->display(); + } + + /** + * Delete a group. + * + * @param int $groupID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($groupID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->group->confirmDelete, $this->createLink('group', 'delete', "groupID=$groupID&confirm=yes"))); + } + else + { + $this->group->delete($groupID); + die(js::locate($this->createLink('group', 'browse'), 'parent')); + } + } +} diff --git a/trunk/module/group/css/browse.css b/trunk/module/group/css/browse.css new file mode 100644 index 0000000000..2ade25db87 --- /dev/null +++ b/trunk/module/group/css/browse.css @@ -0,0 +1 @@ +.user{display:block; width:80px; float:left; overflow:hidden} diff --git a/trunk/module/group/css/managemeber.css b/trunk/module/group/css/managemeber.css new file mode 100644 index 0000000000..ae9db7ee82 --- /dev/null +++ b/trunk/module/group/css/managemeber.css @@ -0,0 +1 @@ +#users span{display:block; width:100px; float:left} diff --git a/trunk/module/group/css/managepriv.css b/trunk/module/group/css/managepriv.css new file mode 100644 index 0000000000..9e708e4256 --- /dev/null +++ b/trunk/module/group/css/managepriv.css @@ -0,0 +1,2 @@ +.table-6 select{width:200px; height:200px} +#version {border:2px solid red; margin-left:10px;} diff --git a/trunk/module/group/js/managepriv.js b/trunk/module/group/js/managepriv.js new file mode 100644 index 0000000000..475d4a927b --- /dev/null +++ b/trunk/module/group/js/managepriv.js @@ -0,0 +1,32 @@ +function showPriv(value) +{ + $('.priv').removeClass('red'); + privs = newPriv[value]; + for(var item in privs) + { + $('#' + privs[item]).addClass('red'); + } +} + +/* Override the setHelpLink(). */ +function setHelpLink(){} + +/** + * Control the actions select control for a module. + * + * @param string $module + * @access public + * @return void + */ +function setModuleActions(module) +{ + $('#actionBox select').addClass('hidden'); // Hide all select first. + $('#actionBox select').val(''); // Unselect all select. + $('.' + module + 'Actions').removeClass('hidden'); // Show the action control for current module. +} + +$(function() +{ + showPriv(version); +} +); diff --git a/trunk/module/group/lang/en.php b/trunk/module/group/lang/en.php new file mode 100644 index 0000000000..1a02d80047 --- /dev/null +++ b/trunk/module/group/lang/en.php @@ -0,0 +1,61 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->group->common = 'Group'; +$lang->group->browse = 'Browse'; +$lang->group->create = 'Create'; +$lang->group->edit = 'Edit'; +$lang->group->copy = 'Copy'; +$lang->group->delete = 'Delete'; +$lang->group->managePriv = 'Privilege'; +$lang->group->managePrivByGroup = 'Privilege'; +$lang->group->managePrivByModule = 'Manage privilege by module'; +$lang->group->manageMember = 'Members'; +$lang->group->linkMember = 'Add members'; +$lang->group->unlinkMember = 'Remove member'; +$lang->group->confirmDelete = 'Are you sure to delete this group?'; +$lang->group->successSaved = 'Success saved.'; +$lang->group->errorNotSaved = 'Not saved, please make sure you have selected some actions and groups.'; + +$lang->group->id = 'Id'; +$lang->group->name = 'Name'; +$lang->group->desc = 'Desc'; +$lang->group->users = 'Users'; +$lang->group->module = 'Module'; +$lang->group->method = 'Method'; +$lang->group->priv = 'Priviledge'; +$lang->group->option = 'Option'; +$lang->group->inside = 'Group users'; +$lang->group->outside = 'Other users'; + +$lang->group->copyOptions['copyPriv'] = 'Copy priviledge'; +$lang->group->copyOptions['copyUser'] = 'Copy user'; + +$lang->group->versions[''] = 'Show methods added in every release'; +$lang->group->versions['3.3'] = '3.3'; +$lang->group->versions['3.2.1'] = '3.2.1'; +$lang->group->versions['3.2'] = '3.2'; +$lang->group->versions['3.1'] = '3.1'; +$lang->group->versions['3.0.beta2'] = '3.0.beta2'; +$lang->group->versions['3.0.beta1'] = '3.0.beta1'; +$lang->group->versions['2.4'] = '2.4'; +$lang->group->versions['2.3'] = '2.3'; +$lang->group->versions['2.2'] = '2.2'; +$lang->group->versions['2.1'] = '2.1'; +$lang->group->versions['2.0'] = '2.0'; +$lang->group->versions['1.5'] = '1.5'; +$lang->group->versions['1.4'] = '1.4'; +$lang->group->versions['1.3'] = '1.3'; +$lang->group->versions['1.2'] = '1.2'; +$lang->group->versions['1.1'] = '1.1'; +$lang->group->versions['1.0.1'] = '1.0.1'; + +include (dirname(__FILE__) . '/resource.php'); diff --git a/trunk/module/group/lang/resource.php b/trunk/module/group/lang/resource.php new file mode 100644 index 0000000000..b192a0f2be --- /dev/null +++ b/trunk/module/group/lang/resource.php @@ -0,0 +1,685 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ + +/* Module order. */ +$lang->moduleOrder[0] = 'index'; +$lang->moduleOrder[5] = 'my'; +$lang->moduleOrder[10] = 'todo'; + +$lang->moduleOrder[15] = 'product'; +$lang->moduleOrder[20] = 'story'; +$lang->moduleOrder[25] = 'productplan'; +$lang->moduleOrder[30] = 'release'; + +$lang->moduleOrder[35] = 'project'; +$lang->moduleOrder[40] = 'task'; +$lang->moduleOrder[45] = 'build'; + +$lang->moduleOrder[50] = 'qa'; +$lang->moduleOrder[55] = 'bug'; +$lang->moduleOrder[60] = 'testcase'; +$lang->moduleOrder[65] = 'testtask'; + +$lang->moduleOrder[70] = 'doc'; +$lang->moduleOrder[75] = 'report'; + +$lang->moduleOrder[80] = 'company'; +$lang->moduleOrder[85] = 'dept'; +$lang->moduleOrder[90] = 'group'; +$lang->moduleOrder[95] = 'user'; + +$lang->moduleOrder[100] = 'admin'; +$lang->moduleOrder[105] = 'extension'; +$lang->moduleOrder[110] = 'action'; + +$lang->moduleOrder[115] = 'svn'; +$lang->moduleOrder[120] = 'search'; +$lang->moduleOrder[125] = 'tree'; +$lang->moduleOrder[130] = 'api'; +$lang->moduleOrder[135] = 'file'; +$lang->moduleOrder[140] = 'misc'; + +/* Index module. */ +$lang->resource->index->index = 'index'; + +$lang->index->methodOrder[0] = 'index'; + +/* My module. */ +$lang->resource->my->index = 'index'; +$lang->resource->my->todo = 'todo'; +$lang->resource->my->task = 'task'; +$lang->resource->my->bug = 'bug'; +$lang->resource->my->testTask = 'testTask'; +$lang->resource->my->testCase = 'testCase'; +$lang->resource->my->story = 'story'; +$lang->resource->my->project = 'myProject'; +$lang->resource->my->profile = 'profile'; +$lang->resource->my->dynamic = 'dynamic'; +$lang->resource->my->editProfile = 'editProfile'; +$lang->resource->my->changePassword = 'changePassword'; + +$lang->my->methodOrder[0] = 'index'; +$lang->my->methodOrder[5] = 'todo'; +$lang->my->methodOrder[10] = 'task'; +$lang->my->methodOrder[15] = 'bug'; +$lang->my->methodOrder[20] = 'testTask'; +$lang->my->methodOrder[25] = 'testCase'; +$lang->my->methodOrder[30] = 'story'; +$lang->my->methodOrder[35] = 'project'; +$lang->my->methodOrder[40] = 'profile'; +$lang->my->methodOrder[45] = 'dynamic'; +$lang->my->methodOrder[50] = 'editProfile'; +$lang->my->methodOrder[55] = 'changePassword'; + +/* Todo. */ +$lang->resource->todo->create = 'create'; +$lang->resource->todo->batchCreate = 'batchCreate'; +$lang->resource->todo->edit = 'edit'; +$lang->resource->todo->view = 'view'; +$lang->resource->todo->delete = 'delete'; +$lang->resource->todo->export = 'export'; +$lang->resource->todo->mark = 'mark'; +$lang->resource->todo->import2Today = 'import2Today'; + +$lang->todo->methodOrder[5] = 'create'; +$lang->todo->methodOrder[10] = 'batchCreate'; +$lang->todo->methodOrder[15] = 'edit'; +$lang->todo->methodOrder[20] = 'view'; +$lang->todo->methodOrder[25] = 'delete'; +$lang->todo->methodOrder[30] = 'export'; +$lang->todo->methodOrder[35] = 'mark'; +$lang->todo->methodOrder[40] = 'import2Today'; + +/* Product. */ +$lang->resource->product->index = 'index'; +$lang->resource->product->browse = 'browse'; +$lang->resource->product->create = 'create'; +$lang->resource->product->view = 'view'; +$lang->resource->product->edit = 'edit'; +$lang->resource->product->order = 'order'; +$lang->resource->product->delete = 'delete'; +$lang->resource->product->roadmap= 'roadmap'; +$lang->resource->product->doc = 'doc'; +$lang->resource->product->dynamic= 'dynamic'; +$lang->resource->product->project= 'project'; +$lang->resource->product->ajaxGetProjects = 'ajaxGetProjects'; +$lang->resource->product->ajaxGetPlans = 'ajaxGetPlans'; + +$lang->product->methodOrder[0] = 'index'; +$lang->product->methodOrder[5] = 'browse'; +$lang->product->methodOrder[10] = 'create'; +$lang->product->methodOrder[15] = 'view'; +$lang->product->methodOrder[20] = 'edit'; +$lang->product->methodOrder[25] = 'order'; +$lang->product->methodOrder[30] = 'delete'; +$lang->product->methodOrder[35] = 'roadmap'; +$lang->product->methodOrder[40] = 'doc'; +$lang->product->methodOrder[45] = 'dynamic'; +$lang->product->methodOrder[50] = 'project'; +$lang->product->methodOrder[55] = 'ajaxGetProjects'; +$lang->product->methodOrder[60] = 'ajaxGetPlans'; + +/* Story. */ +$lang->resource->story->create = 'create'; +$lang->resource->story->batchCreate = 'batchCreate'; +$lang->resource->story->edit = 'edit'; +$lang->resource->story->export = 'export'; +$lang->resource->story->delete = 'delete'; +$lang->resource->story->view = 'view'; +$lang->resource->story->change = 'lblChange'; +$lang->resource->story->review = 'lblReview'; +$lang->resource->story->close = 'lblClose'; +$lang->resource->story->batchClose = 'batchClose'; +$lang->resource->story->activate = 'lblActivate'; +$lang->resource->story->tasks = 'tasks'; +$lang->resource->story->report = 'reportChart'; +$lang->resource->story->ajaxGetProjectStories = 'ajaxGetProjectStories'; +$lang->resource->story->ajaxGetProductStories = 'ajaxGetProductStories'; + +$lang->story->methodOrder[5] = 'create'; +$lang->story->methodOrder[10] = 'batchCreate'; +$lang->story->methodOrder[15] = 'edit'; +$lang->story->methodOrder[20] = 'export'; +$lang->story->methodOrder[25] = 'delete'; +$lang->story->methodOrder[30] = 'view'; +$lang->story->methodOrder[35] = 'change'; +$lang->story->methodOrder[40] = 'review'; +$lang->story->methodOrder[45] = 'close'; +$lang->story->methodOrder[50] = 'batchClose'; +$lang->story->methodOrder[55] = 'activate'; +$lang->story->methodOrder[60] = 'tasks'; +$lang->story->methodOrder[65] = 'report'; +$lang->story->methodOrder[70] = 'ajaxGetProjectStories'; +$lang->story->methodOrder[75] = 'ajaxGetProductStories'; + +/* Product plan. */ +$lang->resource->productplan->browse = 'browse'; +$lang->resource->productplan->create = 'create'; +$lang->resource->productplan->edit = 'edit'; +$lang->resource->productplan->delete = 'delete'; +$lang->resource->productplan->view = 'view'; +$lang->resource->productplan->linkStory = 'linkStory'; +$lang->resource->productplan->unlinkStory = 'unlinkStory'; + +$lang->productplan->methodOrder[5] = 'browse'; +$lang->productplan->methodOrder[10] = 'create'; +$lang->productplan->methodOrder[15] = 'edit'; +$lang->productplan->methodOrder[20] = 'delete'; +$lang->productplan->methodOrder[25] = 'view'; +$lang->productplan->methodOrder[30] = 'linkStory'; +$lang->productplan->methodOrder[35] = 'unlinkStory'; + +/* Release. */ +$lang->resource->release->browse = 'browse'; +$lang->resource->release->create = 'create'; +$lang->resource->release->edit = 'edit'; +$lang->resource->release->delete = 'delete'; +$lang->resource->release->view = 'view'; +$lang->resource->release->export = 'export'; +$lang->resource->release->ajaxGetStoriesAndBugs = 'ajaxGetStoriesAndBugs'; + +$lang->release->methodOrder[5] = 'browse'; +$lang->release->methodOrder[10] = 'create'; +$lang->release->methodOrder[15] = 'edit'; +$lang->release->methodOrder[20] = 'delete'; +$lang->release->methodOrder[25] = 'view'; +$lang->release->methodOrder[30] = 'ajaxGetStoriesAndBugs'; +$lang->release->methodOrder[35] = 'export'; + +/* Project. */ +$lang->resource->project->index = 'index'; +$lang->resource->project->view = 'view'; +$lang->resource->project->browse = 'browse'; +$lang->resource->project->create = 'create'; +$lang->resource->project->edit = 'edit'; +$lang->resource->project->order = 'order'; +$lang->resource->project->delete = 'delete'; +$lang->resource->project->task = 'task'; +$lang->resource->project->grouptask = 'groupTask'; +$lang->resource->project->importtask = 'importTask'; +$lang->resource->project->importBug = 'importBug'; +$lang->resource->project->story = 'story'; +$lang->resource->project->build = 'build'; +$lang->resource->project->testtask = 'testtask'; +$lang->resource->project->bug = 'bug'; +$lang->resource->project->burn = 'burn'; +$lang->resource->project->computeBurn = 'computeBurn'; +$lang->resource->project->burnData = 'burnData'; +$lang->resource->project->team = 'team'; +$lang->resource->project->doc = 'doc'; +$lang->resource->project->dynamic = 'dynamic'; +$lang->resource->project->manageProducts = 'manageProducts'; +//$lang->resource->project->manageChilds = 'manageChilds'; +$lang->resource->project->manageMembers = 'manageMembers'; +$lang->resource->project->unlinkMember = 'unlinkMember'; +$lang->resource->project->linkStory = 'linkStory'; +$lang->resource->project->unlinkStory = 'unlinkStory'; +$lang->resource->project->ajaxGetProducts= 'ajaxGetProducts'; + +$lang->project->methodOrder[0] = 'index'; +$lang->project->methodOrder[5] = 'view'; +$lang->project->methodOrder[10] = 'browse'; +$lang->project->methodOrder[15] = 'create'; +$lang->project->methodOrder[20] = 'edit'; +$lang->project->methodOrder[25] = 'order'; +$lang->project->methodOrder[30] = 'delete'; +$lang->project->methodOrder[35] = 'task'; +$lang->project->methodOrder[40] = 'grouptask'; +$lang->project->methodOrder[45] = 'importtask'; +$lang->project->methodOrder[50] = 'importBug'; +$lang->project->methodOrder[55] = 'story'; +$lang->project->methodOrder[60] = 'build'; +$lang->project->methodOrder[65] = 'testtask'; +$lang->project->methodOrder[70] = 'bug'; +$lang->project->methodOrder[75] = 'burn'; +$lang->project->methodOrder[80] = 'computeBurn'; +$lang->project->methodOrder[85] = 'burnData'; +$lang->project->methodOrder[90] = 'team'; +$lang->project->methodOrder[95] = 'doc'; +$lang->project->methodOrder[100] = 'dynamic'; +$lang->project->methodOrder[105] = 'manageProducts'; +$lang->project->methodOrder[110] = 'manageMembers'; +$lang->project->methodOrder[115] = 'unlinkMember'; +$lang->project->methodOrder[120] = 'linkStory'; +$lang->project->methodOrder[125] = 'unlinkStory'; +$lang->project->methodOrder[130] = 'ajaxGetProducts'; + +/* Task. */ +$lang->resource->task->create = 'create'; +$lang->resource->task->batchCreate = 'batchCreate'; +$lang->resource->task->batchEdit = 'batchEdit'; +$lang->resource->task->edit = 'edit'; +$lang->resource->task->assignTo = 'assign'; +$lang->resource->task->start = 'start'; +$lang->resource->task->finish = 'finish'; +$lang->resource->task->cancel = 'cancel'; +$lang->resource->task->close = 'close'; +$lang->resource->task->batchClose = 'batchClose'; +$lang->resource->task->activate = 'activate'; +$lang->resource->task->delete = 'delete'; +$lang->resource->task->view = 'view'; +$lang->resource->task->export = 'export'; +$lang->resource->task->confirmStoryChange = 'confirmStoryChange'; +$lang->resource->task->ajaxGetUserTasks = 'ajaxGetUserTasks'; +$lang->resource->task->ajaxGetProjectTasks = 'ajaxGetProjectTasks'; +$lang->resource->task->report = 'reportChart'; + +$lang->task->methodOrder[5] = 'create'; +$lang->task->methodOrder[10] = 'batchCreate'; +$lang->task->methodOrder[15] = 'batchEdit'; +$lang->task->methodOrder[20] = 'edit'; +$lang->task->methodOrder[25] = 'assignTo'; +$lang->task->methodOrder[30] = 'start'; +$lang->task->methodOrder[35] = 'finish'; +$lang->task->methodOrder[40] = 'cancel'; +$lang->task->methodOrder[45] = 'close'; +$lang->task->methodOrder[50] = 'batchClose'; +$lang->task->methodOrder[55] = 'activate'; +$lang->task->methodOrder[60] = 'delete'; +$lang->task->methodOrder[65] = 'view'; +$lang->task->methodOrder[70] = 'export'; +$lang->task->methodOrder[75] = 'confirmStoryChange'; +$lang->task->methodOrder[80] = 'ajaxGetUserTasks'; +$lang->task->methodOrder[85] = 'ajaxGetProjectTasks'; +$lang->task->methodOrder[90] = 'report'; + +/* Build. */ +$lang->resource->build->create = 'create'; +$lang->resource->build->edit = 'edit'; +$lang->resource->build->delete = 'delete'; +$lang->resource->build->view = 'view'; +$lang->resource->build->ajaxGetProductBuilds = 'ajaxGetProductBuilds'; +$lang->resource->build->ajaxGetProjectBuilds = 'ajaxGetProjectBuilds'; + +$lang->build->methodOrder[5] = 'create'; +$lang->build->methodOrder[10] = 'edit'; +$lang->build->methodOrder[15] = 'delete'; +$lang->build->methodOrder[20] = 'view'; +$lang->build->methodOrder[25] = 'ajaxGetProductBuilds'; +$lang->build->methodOrder[30] = 'ajaxGetProjectBuilds'; + +/* QA. */ +$lang->resource->qa->index = 'index'; + +$lang->qa->methodOrder[0] = 'index'; + +/* Bug. */ +$lang->resource->bug->index = 'index'; +$lang->resource->bug->browse = 'browse'; +$lang->resource->bug->create = 'create'; +$lang->resource->bug->confirmBug = 'confirmBug'; +$lang->resource->bug->view = 'view'; +$lang->resource->bug->edit = 'edit'; +$lang->resource->bug->assignTo = 'assignTo'; +$lang->resource->bug->resolve = 'resolve'; +$lang->resource->bug->activate = 'activate'; +$lang->resource->bug->close = 'close'; +$lang->resource->bug->report = 'reportChart'; +$lang->resource->bug->export = 'export'; +$lang->resource->bug->confirmStoryChange = 'confirmStoryChange'; +$lang->resource->bug->delete = 'delete'; +$lang->resource->bug->saveTemplate = 'saveTemplate'; +$lang->resource->bug->deleteTemplate = 'deleteTemplate'; +$lang->resource->bug->customFields = 'customFields'; +$lang->resource->bug->ajaxGetUserBugs = 'ajaxGetUserBugs'; +$lang->resource->bug->ajaxGetModuleOwner = 'ajaxGetModuleOwner'; + +$lang->bug->methodOrder[0] = 'index'; +$lang->bug->methodOrder[5] = 'browse'; +$lang->bug->methodOrder[10] = 'create'; +$lang->bug->methodOrder[15] = 'confirmBug'; +$lang->bug->methodOrder[20] = 'view'; +$lang->bug->methodOrder[25] = 'edit'; +$lang->bug->methodOrder[30] = 'assignTo'; +$lang->bug->methodOrder[35] = 'resolve'; +$lang->bug->methodOrder[40] = 'activate'; +$lang->bug->methodOrder[45] = 'close'; +$lang->bug->methodOrder[50] = 'report'; +$lang->bug->methodOrder[55] = 'export'; +$lang->bug->methodOrder[60] = 'confirmStoryChange'; +$lang->bug->methodOrder[65] = 'delete'; +$lang->bug->methodOrder[70] = 'saveTemplate'; +$lang->bug->methodOrder[75] = 'deleteTemplate'; +$lang->bug->methodOrder[80] = 'customFields'; +$lang->bug->methodOrder[85] = 'ajaxGetUserBugs'; +$lang->bug->methodOrder[90] = 'ajaxGetModuleOwner'; + +/* Test case. */ +$lang->resource->testcase->index = 'index'; +$lang->resource->testcase->browse = 'browse'; +$lang->resource->testcase->create = 'create'; +$lang->resource->testcase->batchCreate = 'batchCreate'; +$lang->resource->testcase->view = 'view'; +$lang->resource->testcase->edit = 'edit'; +$lang->resource->testcase->delete = 'delete'; +$lang->resource->testcase->export = 'export'; +$lang->resource->testcase->confirmStoryChange = 'confirmStoryChange'; + +$lang->testcase->methodOrder[0] = 'index'; +$lang->testcase->methodOrder[5] = 'browse'; +$lang->testcase->methodOrder[10] = 'create'; +$lang->testcase->methodOrder[15] = 'batchCreate'; +$lang->testcase->methodOrder[20] = 'view'; +$lang->testcase->methodOrder[25] = 'edit'; +$lang->testcase->methodOrder[30] = 'delete'; +$lang->testcase->methodOrder[35] = 'export'; +$lang->testcase->methodOrder[40] = 'confirmStoryChange'; + +/* Test task. */ +$lang->resource->testtask->index = 'index'; +$lang->resource->testtask->create = 'create'; +$lang->resource->testtask->browse = 'browse'; +$lang->resource->testtask->view = 'view'; +$lang->resource->testtask->cases = 'lblCases'; +$lang->resource->testtask->edit = 'edit'; +$lang->resource->testtask->delete = 'delete'; +$lang->resource->testtask->batchAssign = 'batchAssign'; +$lang->resource->testtask->linkcase = 'linkCase'; +$lang->resource->testtask->unlinkcase = 'lblUnlinkCase'; +$lang->resource->testtask->runcase = 'lblRunCase'; +$lang->resource->testtask->results = 'lblResults'; + +$lang->testtask->methodOrder[0] = 'index'; +$lang->testtask->methodOrder[5] = 'create'; +$lang->testtask->methodOrder[10] = 'browse'; +$lang->testtask->methodOrder[15] = 'view'; +$lang->testtask->methodOrder[20] = 'cases'; +$lang->testtask->methodOrder[25] = 'edit'; +$lang->testtask->methodOrder[30] = 'delete'; +$lang->testtask->methodOrder[35] = 'batchAssign'; +$lang->testtask->methodOrder[40] = 'linkcase'; +$lang->testtask->methodOrder[45] = 'unlinkcase'; +$lang->testtask->methodOrder[50] = 'runcase'; +$lang->testtask->methodOrder[55] = 'results'; + +/* Doc. */ +$lang->resource->doc->index = 'index'; +$lang->resource->doc->browse = 'browse'; +$lang->resource->doc->createLib = 'createLib'; +$lang->resource->doc->editLib = 'editLib'; +$lang->resource->doc->deleteLib = 'deleteLib'; +$lang->resource->doc->create = 'create'; +$lang->resource->doc->view = 'view'; +$lang->resource->doc->edit = 'edit'; +$lang->resource->doc->delete = 'delete'; + +$lang->doc->methodOrder[0] = 'index'; +$lang->doc->methodOrder[5] = 'browse'; +$lang->doc->methodOrder[10] = 'createLib'; +$lang->doc->methodOrder[15] = 'editLib'; +$lang->doc->methodOrder[20] = 'deleteLib'; +$lang->doc->methodOrder[25] = 'create'; +$lang->doc->methodOrder[30] = 'view'; +$lang->doc->methodOrder[35] = 'edit'; +$lang->doc->methodOrder[40] = 'delete'; + +/* Subversion. */ +$lang->resource->svn->diff = 'diff'; +$lang->resource->svn->cat = 'cat'; +$lang->resource->svn->apiSync = 'apiSync'; + +$lang->svn->methodOrder[5] = 'diff'; +$lang->svn->methodOrder[10] = 'cat'; +$lang->svn->methodOrder[15] = 'apiSync'; + +/* Company. */ +$lang->resource->company->index = 'index'; +$lang->resource->company->browse = 'browse'; +$lang->resource->company->edit = 'edit'; +$lang->resource->company->dynamic= 'dynamic'; + +$lang->company->methodOrder[0] = 'index'; +$lang->company->methodOrder[5] = 'browse'; +$lang->company->methodOrder[10] = 'edit'; +$lang->company->methodOrder[15] = 'dynamic'; +$lang->company->methodOrder[20] = 'dffort'; + +/* Department. */ +$lang->resource->dept->browse = 'browse'; +$lang->resource->dept->updateOrder = 'updateOrder'; +$lang->resource->dept->manageChild = 'manageChild'; +$lang->resource->dept->delete = 'delete'; + +$lang->dept->methodOrder[5] = 'browse'; +$lang->dept->methodOrder[10] = 'updateOrder'; +$lang->dept->methodOrder[15] = 'manageChild'; +$lang->dept->methodOrder[20] = 'delete'; + +/* Group. */ +$lang->resource->group->browse = 'browse'; +$lang->resource->group->create = 'create'; +$lang->resource->group->edit = 'edit'; +$lang->resource->group->copy = 'copy'; +$lang->resource->group->delete = 'delete'; +$lang->resource->group->managePriv = 'managePriv'; +$lang->resource->group->manageMember = 'manageMember'; + +$lang->group->methodOrder[5] = 'browse'; +$lang->group->methodOrder[10] = 'create'; +$lang->group->methodOrder[15] = 'edit'; +$lang->group->methodOrder[20] = 'copy'; +$lang->group->methodOrder[25] = 'delete'; +$lang->group->methodOrder[30] = 'managePriv'; +$lang->group->methodOrder[35] = 'manageMember'; + +/* User. */ +$lang->resource->user->create = 'create'; +$lang->resource->user->view = 'view'; +$lang->resource->user->edit = 'edit'; +$lang->resource->user->delete = 'delete'; +$lang->resource->user->todo = 'todo'; +$lang->resource->user->task = 'task'; +$lang->resource->user->bug = 'bug'; +$lang->resource->user->project = 'project'; +$lang->resource->user->dynamic = 'dynamic'; +$lang->resource->user->profile = 'profile'; +$lang->resource->user->ajaxGetUser = 'ajaxGetUser'; + +$lang->user->methodOrder[5] = 'create'; +$lang->user->methodOrder[10] = 'view'; +$lang->user->methodOrder[15] = 'edit'; +$lang->user->methodOrder[20] = 'delete'; +$lang->user->methodOrder[25] = 'todo'; +$lang->user->methodOrder[30] = 'task'; +$lang->user->methodOrder[35] = 'bug'; +$lang->user->methodOrder[40] = 'project'; +$lang->user->methodOrder[45] = 'dynamic'; +$lang->user->methodOrder[50] = 'profile'; +$lang->user->methodOrder[55] = 'ajaxGetUser'; + +/* Tree. */ +$lang->resource->tree->browse = 'browse'; +$lang->resource->tree->updateOrder = 'updateOrder'; +$lang->resource->tree->manageChild = 'manageChild'; +$lang->resource->tree->edit = 'edit'; +$lang->resource->tree->fix = 'fix'; +$lang->resource->tree->delete = 'delete'; +$lang->resource->tree->ajaxGetOptionMenu = 'ajaxGetOptionMenu'; +$lang->resource->tree->ajaxGetSonModules = 'ajaxGetSonModules'; + +$lang->tree->methodOrder[5] = 'browse'; +$lang->tree->methodOrder[10] = 'updateOrder'; +$lang->tree->methodOrder[15] = 'manageChild'; +$lang->tree->methodOrder[20] = 'edit'; +$lang->tree->methodOrder[25] = 'delete'; +$lang->tree->methodOrder[30] = 'ajaxGetOptionMenu'; +$lang->tree->methodOrder[35] = 'ajaxGetSonModules'; + +/* Report. */ +$lang->resource->report->index = 'index'; +$lang->resource->report->projectDeviation = 'projectDeviation'; +$lang->resource->report->productInfo = 'productInfo'; +$lang->resource->report->bugSummary = 'bugSummary'; +$lang->resource->report->bugAssign = 'bugAssign'; +$lang->resource->report->workload = 'workload'; + +$lang->report->methodOrder[0] = 'index'; +$lang->report->methodOrder[5] = 'projectDeviation'; +$lang->report->methodOrder[10] = 'productInfo'; +$lang->report->methodOrder[15] = 'bugSummary'; +$lang->report->methodOrder[17] = 'bugSummary'; +$lang->report->methodOrder[20] = 'workload'; + +/* Search. */ +$lang->resource->search->buildForm = 'buildForm'; +$lang->resource->search->buildQuery = 'buildQuery'; +$lang->resource->search->saveQuery = 'saveQuery'; +$lang->resource->search->deleteQuery = 'deleteQuery'; +$lang->resource->search->select = 'select'; + +$lang->search->methodOrder[5] = 'buildForm'; +$lang->search->methodOrder[10] = 'buildQuery'; +$lang->search->methodOrder[15] = 'saveQuery'; +$lang->search->methodOrder[20] = 'deleteQuery'; +$lang->search->methodOrder[25] = 'select'; + +/* Admin. */ +$lang->resource->admin->index = 'index'; + +$lang->admin->methodOrder[0] = 'index'; + +/* Extension. */ +$lang->resource->extension->browse = 'browse'; +$lang->resource->extension->obtain = 'obtain'; +$lang->resource->extension->structure = 'structure'; +$lang->resource->extension->install = 'install'; +$lang->resource->extension->uninstall = 'uninstall'; +$lang->resource->extension->activate = 'activate'; +$lang->resource->extension->deactivate = 'deactivate'; +$lang->resource->extension->upload = 'upload'; +$lang->resource->extension->erase = 'erase'; +$lang->resource->extension->upgrade = 'upgrade'; + +/* Others. */ +$lang->resource->api->getModel = 'getModel'; + +$lang->api->methodOrder[5] = 'getModel'; + +$lang->resource->file->download = 'download'; +$lang->resource->file->edit = 'edit'; +$lang->resource->file->delete = 'delete'; +$lang->resource->file->ajaxUpload = 'ajaxUpload'; + +$lang->file->methodOrder[5] = 'download'; +$lang->file->methodOrder[10] = 'edit'; +$lang->file->methodOrder[15] = 'delete'; +$lang->file->methodOrder[20] = 'ajaxUpload'; + +$lang->resource->misc->ping = 'ping'; + +$lang->misc->methodOrder[5] = 'ping'; + +$lang->resource->action->trash = 'trash'; +$lang->resource->action->undelete = 'undelete'; + +$lang->action->methodOrder[5] = 'trash'; +$lang->action->methodOrder[10] = 'undelete'; + +/* Every version of new privilege. */ +$lang->changelog['1.0.1'][] = 'project-computeBurn'; + +$lang->changelog['1.1'][] = 'search-saveQuery'; +$lang->changelog['1.1'][] = 'search-deleteQuery'; + +$lang->changelog['1.2'][] = 'product-doc'; +$lang->changelog['1.2'][] = 'project-doc'; +$lang->changelog['1.2'][] = 'project-ajaxGetProducts'; +$lang->changelog['1.2'][] = 'bug-saveTemplate'; +$lang->changelog['1.2'][] = 'bug-deleteTemplate'; +$lang->changelog['1.2'][] = 'bug-customFields'; +$lang->changelog['1.2'][] = 'bug-ajaxGetModuleOwner'; +$lang->changelog['1.2'][] = 'doc-index'; +$lang->changelog['1.2'][] = 'doc-browse'; +$lang->changelog['1.2'][] = 'doc-createLib'; +$lang->changelog['1.2'][] = 'doc-editLib'; +$lang->changelog['1.2'][] = 'doc-deleteLib'; +$lang->changelog['1.2'][] = 'doc-create'; +$lang->changelog['1.2'][] = 'doc-view'; +$lang->changelog['1.2'][] = 'doc-edit'; +$lang->changelog['1.2'][] = 'doc-delete'; +$lang->changelog['1.2'][] = 'doc-deleteFile'; + +$lang->changelog['1.3'][] = 'task-start'; +$lang->changelog['1.3'][] = 'task-complete'; +$lang->changelog['1.3'][] = 'task-cancel'; +$lang->changelog['1.3'][] = 'tree-ajaxGetSonModules'; +$lang->changelog['1.3'][] = 'file-delete'; +$lang->changelog['1.3'][] = 'file-ajaxUpload'; + +$lang->changelog['1.4'][] = 'my-testTask'; +$lang->changelog['1.4'][] = 'my-testCase'; +$lang->changelog['1.4'][] = 'task-finish'; +$lang->changelog['1.4'][] = 'task-close'; +$lang->changelog['1.4'][] = 'task-activate'; +$lang->changelog['1.4'][] = 'search-select'; + +$lang->changelog['1.5'][] = 'task-batchClose'; + +$lang->changelog['2.0'][] = 'my-dynamic'; +$lang->changelog['2.0'][] = 'bug-export'; +$lang->changelog['2.0'][] = 'story-export'; +$lang->changelog['2.0'][] = 'story-reportChart'; +$lang->changelog['2.0'][] = 'task-export'; +$lang->changelog['2.0'][] = 'task-reportChart'; +$lang->changelog['2.0'][] = 'taskcase-export'; +$lang->changelog['2.0'][] = 'company-dynamic'; +$lang->changelog['2.0'][] = 'user-dynamic'; +$lang->changelog['2.0'][] = 'extension-browse'; +$lang->changelog['2.0'][] = 'extension-obtain'; +$lang->changelog['2.0'][] = 'extension-install'; +$lang->changelog['2.0'][] = 'extension-uninstall'; +$lang->changelog['2.0'][] = 'extension-activate'; +$lang->changelog['2.0'][] = 'extension-deactivate'; +$lang->changelog['2.0'][] = 'extension-upload'; +$lang->changelog['2.0'][] = 'extension-erase'; + +$lang->changelog['2.1'][] = 'extension-upgrade'; + +$lang->changelog['2.2'][] = 'file-edit'; + +$lang->changelog['2.3'][] = 'product-dynamic'; +$lang->changelog['2.3'][] = 'project-dynamic'; +$lang->changelog['2.3'][] = 'project-importBug'; +$lang->changelog['2.3'][] = 'story-batchCreate'; +$lang->changelog['2.3'][] = 'task-batchCreate'; +$lang->changelog['2.3'][] = 'testcase-batchCreate'; +$lang->changelog['2.3'][] = 'bug-confirmBug'; +$lang->changelog['2.3'][] = 'svn-diff'; +$lang->changelog['2.3'][] = 'svn-cat'; +$lang->changelog['2.3'][] = 'svn-apiSync'; + +$lang->changelog['2.4'][] = 'user-ajaxGetUser'; +$lang->changelog['2.4'][] = 'task-assign'; +$lang->changelog['2.4'][] = 'project-testtask'; +$lang->changelog['2.4'][] = 'todo-export'; +$lang->changelog['2.4'][] = 'product-project'; + +$lang->changelog['3.0.beta1'][] = 'release-ajaxGetStoriesAndBugs'; + +$lang->changelog['3.0.beta2'][] = 'extension-structure'; +$lang->changelog['3.0.beta2'][] = 'product-order'; +$lang->changelog['3.0.beta2'][] = 'project-order'; + +$lang->changelog['3.1'][] = 'todo-batchCreate'; + +$lang->changelog['3.2'][] = 'my-changePassword'; +$lang->changelog['3.2'][] = 'story-batchClose'; +$lang->changelog['3.2'][] = 'task-batchEdit'; +$lang->changelog['3.2'][] = 'release-export'; +$lang->changelog['3.2'][] = 'report-index'; +$lang->changelog['3.2'][] = 'report-projectDeviation'; +$lang->changelog['3.2'][] = 'report-productInfo'; +$lang->changelog['3.2'][] = 'report-bugSummary'; +$lang->changelog['3.2'][] = 'report-workload'; +$lang->changelog['3.2'][] = 'tree-fix'; + +$lang->changelog['3.3'][] = 'report-bugAssign'; diff --git a/trunk/module/group/lang/zh-cn.php b/trunk/module/group/lang/zh-cn.php new file mode 100644 index 0000000000..d1153f7775 --- /dev/null +++ b/trunk/module/group/lang/zh-cn.php @@ -0,0 +1,61 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->group->common = '权限分组'; +$lang->group->browse = '浏览分组'; +$lang->group->create = '新增分组'; +$lang->group->edit = '编辑分组'; +$lang->group->copy = '复制分组'; +$lang->group->delete = '删除分组'; +$lang->group->managePriv = '权限维护'; +$lang->group->managePrivByGroup = '权限维护'; +$lang->group->managePrivByModule = '按模块分配权限'; +$lang->group->manageMember = '成员维护'; +$lang->group->linkMember = '关联用户'; +$lang->group->unlinkMember = '移除用户'; +$lang->group->confirmDelete = '您确定删除该用户分组吗?'; +$lang->group->successSaved = '成功保存'; +$lang->group->errorNotSaved = '没有保存,请确认选择了权限数据。'; + +$lang->group->id = '编号'; +$lang->group->name = '分组名称'; +$lang->group->desc = '分组描述'; +$lang->group->users = '用户列表'; +$lang->group->module = '模块'; +$lang->group->method = '方法'; +$lang->group->priv = '权限'; +$lang->group->option = '选项'; +$lang->group->inside = '组内用户'; +$lang->group->outside = '组外用户'; + +$lang->group->copyOptions['copyPriv'] = '复制权限'; +$lang->group->copyOptions['copyUser'] = '复制用户'; + +$lang->group->versions[''] = '显示各版本新增权限'; +$lang->group->versions['3.3'] = '禅道3.3'; +$lang->group->versions['3.2.1'] = '禅道3.2.1'; +$lang->group->versions['3.2'] = '禅道3.2'; +$lang->group->versions['3.1'] = '禅道3.1'; +$lang->group->versions['3.0.beta2'] = '禅道3.0.beta2'; +$lang->group->versions['3.0.beta1'] = '禅道3.0.beta1'; +$lang->group->versions['2.4'] = '禅道2.4'; +$lang->group->versions['2.3'] = '禅道2.3'; +$lang->group->versions['2.2'] = '禅道2.2'; +$lang->group->versions['2.1'] = '禅道2.1'; +$lang->group->versions['2.0'] = '禅道2.0'; +$lang->group->versions['1.5'] = '禅道1.5'; +$lang->group->versions['1.4'] = '禅道1.4'; +$lang->group->versions['1.3'] = '禅道1.3'; +$lang->group->versions['1.2'] = '禅道1.2'; +$lang->group->versions['1.1'] = '禅道1.1'; +$lang->group->versions['1.0.1'] = '禅道1.0.1'; + +include (dirname(__FILE__) . '/resource.php'); diff --git a/trunk/module/group/lang/zh-tw.php b/trunk/module/group/lang/zh-tw.php new file mode 100644 index 0000000000..a89d2a0ce2 --- /dev/null +++ b/trunk/module/group/lang/zh-tw.php @@ -0,0 +1,61 @@ + + * @package group + * @version $Id: zh-tw.php 3492 2012-09-02 07:44:37Z shiyangyangwork@yahoo.cn $ + * @link http://www.zentao.net + */ +$lang->group->common = '權限分組'; +$lang->group->browse = '瀏覽分組'; +$lang->group->create = '新增分組'; +$lang->group->edit = '編輯分組'; +$lang->group->copy = '複製分組'; +$lang->group->delete = '刪除分組'; +$lang->group->managePriv = '權限維護'; +$lang->group->managePrivByGroup = '權限維護'; +$lang->group->managePrivByModule = '按模組分配權限'; +$lang->group->manageMember = '成員維護'; +$lang->group->linkMember = '關聯用戶'; +$lang->group->unlinkMember = '移除用戶'; +$lang->group->confirmDelete = '您確定刪除該用戶分組嗎?'; +$lang->group->successSaved = '成功保存'; +$lang->group->errorNotSaved = '沒有保存,請確認選擇了權限數據。'; + +$lang->group->id = '編號'; +$lang->group->name = '分組名稱'; +$lang->group->desc = '分組描述'; +$lang->group->users = '用戶列表'; +$lang->group->module = '模組'; +$lang->group->method = '方法'; +$lang->group->priv = '權限'; +$lang->group->option = '選項'; +$lang->group->inside = '組內用戶'; +$lang->group->outside = '組外用戶'; + +$lang->group->copyOptions['copyPriv'] = '複製權限'; +$lang->group->copyOptions['copyUser'] = '複製用戶'; + +$lang->group->versions[''] = '顯示各版本新增權限'; +$lang->group->versions['3.3'] = '禪道3.3'; +$lang->group->versions['3.2.1'] = '禪道3.2.1'; +$lang->group->versions['3.2'] = '禪道3.2'; +$lang->group->versions['3.1'] = '禪道3.1'; +$lang->group->versions['3.0.beta2'] = '禪道3.0.beta2'; +$lang->group->versions['3.0.beta1'] = '禪道3.0.beta1'; +$lang->group->versions['2.4'] = '禪道2.4'; +$lang->group->versions['2.3'] = '禪道2.3'; +$lang->group->versions['2.2'] = '禪道2.2'; +$lang->group->versions['2.1'] = '禪道2.1'; +$lang->group->versions['2.0'] = '禪道2.0'; +$lang->group->versions['1.5'] = '禪道1.5'; +$lang->group->versions['1.4'] = '禪道1.4'; +$lang->group->versions['1.3'] = '禪道1.3'; +$lang->group->versions['1.2'] = '禪道1.2'; +$lang->group->versions['1.1'] = '禪道1.1'; +$lang->group->versions['1.0.1'] = '禪道1.0.1'; + +include (dirname(__FILE__) . '/resource.php'); diff --git a/trunk/module/group/model.php b/trunk/module/group/model.php new file mode 100644 index 0000000000..4eb3559210 --- /dev/null +++ b/trunk/module/group/model.php @@ -0,0 +1,303 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> +specialChars('name, desc')->get(); + return $this->dao->insert(TABLE_GROUP)->data($group)->batchCheck($this->config->group->create->requiredFields, 'notempty')->exec(); + } + + /** + * Update a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function update($groupID) + { + $group = fixer::input('post')->specialChars('name, desc')->get(); + return $this->dao->update(TABLE_GROUP)->data($group)->batchCheck($this->config->group->edit->requiredFields, 'notempty')->where('id')->eq($groupID)->exec(); + } + + /** + * Copy a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function copy($groupID) + { + $group = fixer::input('post')->specialChars('name, desc')->remove('options')->get(); + $this->dao->insert(TABLE_GROUP)->data($group)->check('name', 'unique')->check('name', 'notempty')->exec(); + if($this->post->options == false) return; + if(!dao::isError()) + { + $newGroupID = $this->dao->lastInsertID(); + $options = join(',', $this->post->options); + if(strpos($options, 'copyPriv') !== false) $this->copyPriv($groupID, $newGroupID); + if(strpos($options, 'copyUser') !== false) $this->copyUser($groupID, $newGroupID); + } + } + + /** + * Copy privileges. + * + * @param string $fromGroup + * @param string $toGroup + * @access public + * @return void + */ + public function copyPriv($fromGroup, $toGroup) + { + $privs = $this->dao->findByGroup($fromGroup)->from(TABLE_GROUPPRIV)->fetchAll(); + foreach($privs as $priv) + { + $priv->group = $toGroup; + $this->dao->insert(TABLE_GROUPPRIV)->data($priv)->exec(); + } + } + + /** + * Copy user. + * + * @param string $fromGroup + * @param string $toGroup + * @access public + * @return void + */ + public function copyUser($fromGroup, $toGroup) + { + $users = $this->dao->findByGroup($fromGroup)->from(TABLE_USERGROUP)->fetchAll(); + foreach($users as $user) + { + $user->group = $toGroup; + $this->dao->insert(TABLE_USERGROUP)->data($user)->exec(); + } + } + + /** + * Get group lists. + * + * @param int $companyID + * @access public + * @return array + */ + public function getList($companyID) + { + return $this->dao->findByCompany($companyID)->from(TABLE_GROUP)->fetchAll(); + } + + /** + * Get group pairs. + * + * @access public + * @return array + */ + public function getPairs() + { + return $this->dao->findByCompany($this->app->company->id)->fields('id, name')->from(TABLE_GROUP)->fetchPairs(); + } + + /** + * Get group by id. + * + * @param int $groupID + * @access public + * @return object + */ + public function getByID($groupID) + { + return $this->dao->findById($groupID)->from(TABLE_GROUP)->fetch(); + } + + /** + * Get privileges of a groups. + * + * @param int $groupID + * @access public + * @return array + */ + public function getPrivs($groupID) + { + $privs = array(); + $stmt = $this->dao->select('module, method')->from(TABLE_GROUPPRIV)->where('`group`')->eq($groupID)->orderBy('module')->query(); + while($priv = $stmt->fetch()) $privs[$priv->module][$priv->method] = $priv->method; + return $privs; + } + + /** + * Get user pairs of a group. + * + * @param int $groupID + * @access public + * @return array + */ + public function getUserPairs($groupID) + { + return $this->dao->select('t2.account, t2.realname') + ->from(TABLE_USERGROUP)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account') + ->where('`group`')->eq((int)$groupID) + ->andWhere('t2.deleted')->eq(0) + ->andWhere('t2.company')->eq($this->app->company->id) + ->orderBy('t2.account') + ->fetchPairs(); + } + + /** + * Delete a group. + * + * @param int $groupID + * @access public + * @return void + */ + public function delete($groupID) + { + $this->dao->delete()->from(TABLE_GROUP)->where('id')->eq($groupID)->exec(); + $this->dao->delete()->from(TABLE_USERGROUP)->where('`group`')->eq($groupID)->exec(); + $this->dao->delete()->from(TABLE_GROUPPRIV)->where('`group`')->eq($groupID)->exec(); + } + + /** + * Update privilege of a group. + * + * @param int $groupID + * @access public + * @return bool + */ + public function updatePrivByGroup($groupID) + { + /* Delete old. */ + $this->dao->delete()->from(TABLE_GROUPPRIV)->where('`group`')->eq($groupID)->exec(); + + /* Insert new. */ + foreach($this->post->actions as $moduleName => $moduleActions) + { + foreach($moduleActions as $actionName) + { + $data->group = $groupID; + $data->module = $moduleName; + $data->method = $actionName; + $this->dao->insert(TABLE_GROUPPRIV)->data($data)->exec(); + } + } + return true; + } + + /** + * Update privilege by module. + * + * @access public + * @return void + */ + public function updatePrivByModule() + { + if($this->post->module == false or $this->post->actions == false or $this->post->groups == false) return false; + + foreach($this->post->actions as $action) + { + foreach($this->post->groups as $group) + { + $data->group = $group; + $data->module = $this->post->module; + $data->method = $action; + $this->dao->replace(TABLE_GROUPPRIV)->data($data)->exec(); + } + } + return true; + } + + /** + * Update users. + * + * @param int $groupID + * @access public + * @return void + */ + public function updateUser($groupID) + { + /* Delete old. */ + $this->dao->delete()->from(TABLE_USERGROUP)->where('`group`')->eq($groupID)->exec(); + + /* Insert new. */ + if($this->post->members == false) return; + foreach($this->post->members as $account) + { + $data->account = $account; + $data->group = $groupID; + $this->dao->insert(TABLE_USERGROUP)->data($data)->exec(); + } + } + + /** + * Sort resource. + * + * @access public + * @return void + */ + public function sortResource() + { + $resources = $this->lang->resource; + unset($this->lang->resource); + + /* sort moduleOrder. */ + ksort($this->lang->moduleOrder, SORT_ASC); + foreach($this->lang->moduleOrder as $moduleName) + { + $resource = $resources->$moduleName; + unset($resources->$moduleName); + $this->lang->resource->$moduleName = $resource; + } + foreach($resources as $key => $resource) + { + $this->lang->resource->$key = $resource; + } + + /* sort methodOrder. */ + foreach($this->lang->resource as $moduleName => $resources) + { + $resources = (array)$resources; + if(isset($this->lang->$moduleName->methodOrder)) + { + ksort($this->lang->$moduleName->methodOrder, SORT_ASC); + foreach($this->lang->$moduleName->methodOrder as $key) + { + if(isset($resources[$key])) + { + $tmpResources->$key = $resources[$key]; + unset($resources[$key]); + } + } + if($resources) + { + foreach($resources as $key => $resource) + { + $tmpResources->$key = $resource; + } + } + $this->lang->resource->$moduleName = $tmpResources; + unset($tmpResources); + } + } + } +} diff --git a/trunk/module/group/view/browse.html.php b/trunk/module/group/view/browse.html.php new file mode 100644 index 0000000000..fcdab317f3 --- /dev/null +++ b/trunk/module/group/view/browse.html.php @@ -0,0 +1,50 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      group->browse;?>
      +
      group->create);?>
      +
      group->id;?>group->name;?>group->desc;?>group->users;?>actions;?>
      id;?>name;?>desc;?>id] as $user) echo "$user";?> + id", $lang->group->managePrivByGroup);?> + id", $lang->group->manageMember);?> + id", $lang->edit);?> + id", $lang->copy);?> + id", $lang->delete, "hiddenwin");?> +
      group->managePrivByModule, inlink('managePriv', 'type=byModule'));?>
      + diff --git a/trunk/module/group/view/copy.html.php b/trunk/module/group/view/copy.html.php new file mode 100644 index 0000000000..7d1d5d03d4 --- /dev/null +++ b/trunk/module/group/view/copy.html.php @@ -0,0 +1,32 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + +
      group->copy;?>
      group->name;?>name, "class='text-1'");?>
      group->desc;?>desc, "rows='5' class='area-1'");?>
      group->option;?>group->copyOptions);?>
      +
      + diff --git a/trunk/module/group/view/create.html.php b/trunk/module/group/view/create.html.php new file mode 100644 index 0000000000..e518593d44 --- /dev/null +++ b/trunk/module/group/view/create.html.php @@ -0,0 +1,29 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + +
      group->create;?>
      group->name;?>
      group->desc;?>
      +
      + diff --git a/trunk/module/group/view/edit.html.php b/trunk/module/group/view/edit.html.php new file mode 100644 index 0000000000..24863ff047 --- /dev/null +++ b/trunk/module/group/view/edit.html.php @@ -0,0 +1,28 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + +
      group->edit;?>
      group->name;?>name, "class='text-1'");?>
      group->desc;?>desc, "rows='5' class='area-1'");?>
      +
      + diff --git a/trunk/module/group/view/managemember.html.php b/trunk/module/group/view/managemember.html.php new file mode 100644 index 0000000000..cd19f7cede --- /dev/null +++ b/trunk/module/group/view/managemember.html.php @@ -0,0 +1,47 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + +
      name . $lang->colon . $lang->group->manageMember;?>
      group->inside;?> + $realname):?> +
      ' . html::checkbox('members', array($account => $realname), $account) . '';?>
      + "; $i ++;?> + +
      group->outside;?> + $realname):?> +
      ' . html::checkbox('members', array($account => $realname), '') . '';?>
      + "; $i ++;?> + +
      + goback, $this->createLink('group', 'browse')); + echo html::hidden('foo'); // Just a var, to make sure $_POST is not empty. + ?> +
      +
      + diff --git a/trunk/module/group/view/managepriv.html.php b/trunk/module/group/view/managepriv.html.php new file mode 100644 index 0000000000..057cc326bb --- /dev/null +++ b/trunk/module/group/view/managepriv.html.php @@ -0,0 +1,17 @@ + + * @package group + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + * @package group + * @version $Id: managepriv.html.php 1517 2011-03-07 10:02:57Z wwccss $ + * @link http://www.zentao.net + */ +?> +
      + + + + + + + resource as $moduleName => $moduleActions):?> + '> + + + + + + + +
      + name . $lang->colon . $lang->group->managePriv; + echo html::select('version', $this->lang->group->versions, '', "onchange=showPriv(this.value)"); + ?> +
      group->module;?>group->method;?>
      lang->$moduleName->common;?> + + + $actionLabel):?> +
      + /> + >$moduleName->$actionLabel;?> +
      + "; $i ++;?> + +
      selectAll . html::selectAll('', 'checkbox')?> + save); + echo html::linkButton($lang->goback, $this->createLink('group', 'browse')); + echo html::hidden('foo'); // Just a hidden var, to make sure $_POST is not empty. + ?> +
      +
      + diff --git a/trunk/module/group/view/privbymodule.html.php b/trunk/module/group/view/privbymodule.html.php new file mode 100644 index 0000000000..87de1d036c --- /dev/null +++ b/trunk/module/group/view/privbymodule.html.php @@ -0,0 +1,45 @@ + + * @package group + * @version $Id: managepriv.html.php 1517 2011-03-07 10:02:57Z wwccss $ + * @link http://www.zentao.net + */ +?> +
      + + + + + + + + + + + + + + + +
      group->managePriv;?>
      group->module;?>group->method;?>group->common;?>
      + $moduleActions) + { + echo html::select('actions[]', $moduleActions, '', "multiple='multiple' class='$class {$module}Actions'"); + $class = 'hidden'; + } + ?> +
      + save); + echo html::linkButton($lang->goback, $this->createLink('group', 'browse')); + echo html::hidden('foo'); // Just make $_POST not empty.. + ?> +
      +
      diff --git a/trunk/module/help/control.php b/trunk/module/help/control.php new file mode 100644 index 0000000000..2b25a20c30 --- /dev/null +++ b/trunk/module/help/control.php @@ -0,0 +1,46 @@ + + * @package ZenTaoPMS + * @version $Id$ + * @link http://www.zentao.net + */ +class help extends control +{ + /** + * Get the help info of a field.. + * + * @param string $module + * @param string $method + * @param string $field + * @param string $clientLang + * @access public + * @return void + */ + public function field($module, $method, $field) + { + $clientLang = $this->app->getClientLang(); + include "./lang/field.$clientLang.php"; + + $fieldName = ''; + $fieldNote = $this->lang->help->noHelpYet; + if(isset($help->$module->$field)) + { + $fieldHelp = explode('|', $help->$module->$field); + $fieldName = $fieldHelp[0]; + if(isset($fieldHelp[1])) $fieldNote = $fieldHelp[1]; + } + elseif($field == 'labels') + { + list($fieldName, $fieldNote) = explode('|', $help->file->labels); + } + $this->view->header->title = $fieldName; + $this->view->fieldName = $fieldName; + $this->view->fieldNote = $fieldNote; + $this->display(); + } +} diff --git a/trunk/module/help/lang/en.php b/trunk/module/help/lang/en.php new file mode 100644 index 0000000000..6f87ae5baf --- /dev/null +++ b/trunk/module/help/lang/en.php @@ -0,0 +1,2 @@ +help->noHelpYet = 'No help yet.'; diff --git a/trunk/module/help/lang/field.en.php b/trunk/module/help/lang/field.en.php new file mode 100644 index 0000000000..71437f42a9 --- /dev/null +++ b/trunk/module/help/lang/field.en.php @@ -0,0 +1,211 @@ +file->labels = 'Attatch file name|You can specify the file name manually. If empty, use the file name.'; + +$help->bug->product = 'Product|Set product of the bug.'; +$help->bug->module = 'Module|Note: the modules of bug, testcase and stories are seperated.'; +$help->bug->project = 'Project| The project this bug belongs to.'; +$help->bug->story = 'Story|The related story for this bug.'; +$help->bug->task = 'Task|The related task.'; +$help->bug->title = 'Title|The most important field for bug, should be clean and meaing.'; +$help->bug->severity = 'Severity|The severity of the bug, 1 is most severity. Also the team can define the severity themselves.'; +$help->bug->pri = "Priority|The priority of the bug, it's should be defind by the project manager or the QA manager."; +$help->bug->type = 'Bug type|'; +$help->bug->os = 'OS|'; +$help->bug->browser = 'Browser'; +$help->bug->steps = 'Reproduce steps|Very important, should list the steps clean thus the developer can reproduct it.。'; +$help->bug->status = 'Bug status'; +$help->bug->mailto = 'Mailto|You can input the user account to select users to mail to.'; +$help->bug->openedby = 'OpenedBy|The opener of the bug.'; +$help->bug->openedbuild = 'Opened build|If the build is empty, should add them in project view.'; +$help->bug->assignedto = 'AssignedTo|'; +$help->bug->resolvedby = 'ResolvedBy|'; +$help->bug->resolution = 'Resolution|'; +$help->bug->resolvedbuild = 'Resolved build|In which build this bug was fixed.'; +$help->bug->closedby = 'Closed by|'; +$help->bug->closeddate = 'Closed date|'; +$help->bug->duplicatebug = 'Duplicated bug|'; +$help->bug->linkbug = 'Related bug| You can input more bugs, use "," to join theme.'; +$help->bug->case = 'Related testcase|'; +$help->bug->keywords = 'Keywords|'; + +$help->build->product = 'Product|The product which the build belongs to.'; +$help->build->project = 'Project|The project whcic the build belongs to'; +$help->build->name = 'Build name|The build name, for example zentaopms.1.5.beta1.20110315'; +$help->build->date = 'Build date|The building date'; +$help->build->builder = 'Builder|Builder'; +$help->build->scmpath = 'Source url|If the team is useing subversion and so on, can set the tag url path.'; +$help->build->filepath = 'Package path|The file path of the package.'; +$help->build->desc = 'Description|Finished tasks, stories or fixed bugs'; + +$help->company->name = 'Company name'; +$help->company->phone = 'Phone'; +$help->company->fax = 'Fax'; +$help->company->address = 'Address'; +$help->company->zipcode = 'zipcode'; +$help->company->website = 'The web site|The web site of the company, appears at the top left menu. With http://.'; +$help->company->backyard = 'The intranet site|The intranet site of the company, with http://.'; +$help->company->pms = 'The zentao site.|The domain of the zentao pms, just domain, no http.'; +$help->company->guest = 'Allow guest user login?|If set to true, you should create a user group named guest and grant priviledges to it.'; + +$help->convert->dbhost = 'Database host|The source database host.'; +$help->convert->dbport = 'Database port|In most case it is 3306.'; +$help->convert->dbuser = 'Database user'; +$help->convert->dbpassword = 'Database password|'; +$help->convert->dbname = 'Database name|。'; +$help->convert->dbprefix = 'The prefix of the table'; +$help->convert->installpath= 'The install path of the source system.'; + +$help->dept->depts = 'Sub departments'; +$help->dept->orders = 'Order nubmer'; + +$help->group->name = 'Group name|If the company allowes guest login, you should create a group named "guest"'; +$help->group->desc = 'Group description'; + +$help->install->webroot = 'The web root of zentao|When installing, the system will compute the root auto. You can change it in config/my.php.'; +$help->install->requesttype = 'The request type|GET or PATH_INFO, the GET type can work in any case. But PATH_INFO needs you to set up the rewrite feature in apache.'; +$help->install->defaultlang = 'Default language|The default language'; +$help->install->dbhost = 'Database host|In most case is localhost, also you can try 127.0.0.1'; +$help->install->dbport = 'Database port|In most case it is 3306.'; +$help->install->dbuser = 'The database user'; +$help->install->dbpassword = 'The database password|。'; +$help->install->dbname = 'The database name zentao to use'; +$help->install->dbprefix = 'The prefix of tables|To avoid conflics with other systems with same table name.'; +$help->install->cleardb = 'Clear data|If there is alread a database with the same name, you can try clear data to reinstall.'; +$help->install->company = 'Company|Company name.'; +$help->install->pms = 'The pms site.|The system will compute it auto, don not change it if you are not sure.'; +$help->install->account = 'Admina account|This admin user ia the super admin'; +$help->install->password = 'Admin password'; + +$help->product->name = 'Product name'; +$help->product->code = 'Product code'; +$help->product->po = 'Product owner|Responsible for stories of this product.'; +$help->product->rm = 'Release manager|The people resonsible for building packages and release them.'; +$help->product->qm = 'Test manager'; +$help->product->status = 'Status'; +$help->product->desc = 'Description'; + +$help->productplan->product = 'Product'; +$help->productplan->title = 'Title'; +$help->productplan->desc = 'Desc'; +$help->productplan->begin = 'Begin date'; +$help->productplan->end = 'End date'; + +$help->project->name = 'Project name'; +$help->project->code = 'Project code'; +$help->project->begin = 'Begin date'; +$help->project->end = "End date|If you are using scrum it should no more than 30 days"; +$help->project->team = 'Team name'; +$help->project->status = 'Project status|Only runing projects can appears in the home page'; +$help->project->desc = 'Description'; +$help->project->goal = 'The goal of the project'; +$help->project->updateburn = 'Update burn down chart|
      1. Click the "update burndown" link to update it manually.
      2. Update it by cron. + for more, see How to update burndown chart.'; + +$help->release->product = 'Product'; +$help->release->build = 'Build|The related build.'; +$help->release->name = 'Release name|For example, zentaopms1.5 stable'; +$help->release->date = 'Release date'; +$help->release->desc = 'Release description|Can be change log, install help and so on'; + +$help->story->product = 'Product'; +$help->story->module = 'Module|Can use module to manage story.'; +$help->story->plan = 'Product plan|Through plan, can give the team a overview of the product'; +$help->story->title = 'Title'; +$help->story->spec = 'The story spec'; +$help->story->spec = 'The story verify'; +$help->story->pri = 'Priority|When selected to project, the story should be ordered by the priority field first.'; +$help->story->estimate = 'Estimate|Estimate the story point.'; +$help->story->status = 'Status|Only the active story can be added to a project. The default status is draft, so should be reviewed.'; +$help->story->stage = 'Developping stage'; +$help->story->mailto = 'Mail to'; +$help->story->openedby = 'Opened by'; +$help->story->openeddate = 'Opened date'; +$help->story->assignedto = 'Assigned to|Who ownes to this story now'; +$help->story->assigneddate = 'Assgigned date'; +$help->story->closedby = 'Closed by'; +$help->story->closeddate = 'Closed date'; +$help->story->closedreason = 'Closed reason|When a story closed, should give the closed reason'; +$help->story->rejectedreason = 'Rejected reason|When a story is rejected, should give the rejected reason'; +$help->story->reviewedby = 'Reviewed by|Who reviewed the story, can be some peoples in a meeting'; +$help->story->revieweddate = 'Reviewed date'; +$help->story->comment = 'Comment'; +$help->story->linkstories = 'Related stories|can input the story id, seperated by ","'; +$help->story->childstories = 'Child stories|If the story is to huge, it can be divided into some child stories.'; +$help->story->duplicatestory = 'Duplicated story'; +$help->story->reviewresult = 'Review result'; +$help->story->keywords = 'Keywords'; +$help->story->neednotreview = 'Need not review|If you are the owner of the proudct, can check it.'; + +$help->task->project = 'Project'; +$help->task->story = 'Related Story'; +$help->task->name = 'Task name'; +$help->task->type = 'Task type'; +$help->task->pri = 'Task priority'; +$help->task->assignedto = 'Assigned to|The task owner.'; +$help->task->estimate = 'Estimate|The time estimated for this task'; +$help->task->left = 'Left hour|The left hours estimated. Should updated every day, thus to draw the burndown chart.'; +$help->task->consumed = 'Consumed|The consumed times for this task'; +$help->task->deadline = 'Deadline|the deadline of this task'; +$help->task->status = 'Status'; +$help->task->desc = 'Description'; + +$help->testcase->product = 'Product'; +$help->testcase->module = 'Module|The modules of testcase is seperated from story modules.'; +$help->testcase->story = 'Story|The related story。'; +$help->testcase->title = 'Title|The title of the test case.'; +$help->testcase->pri = 'Priority'; +$help->testcase->type = 'Test case type|'; +$help->testcase->status = 'Status'; +$help->testcase->steps = 'Case steps|'; +$help->testcase->openedby = 'OpenedBy'; +$help->testcase->openeddate = 'OpenedDate'; +$help->testcase->result = 'Test result|'; +$help->testcase->real = 'The real|The real result of the test case.'; +$help->testcase->keywords = 'Keywords'; +$help->testcase->linkcase = 'Related cases.'; +$help->testcase->stage = 'Applicative stage'; + +$help->testtask->product = 'Product|Belongs to which product.'; +$help->testtask->project = 'Project|Belongs to which project.'; +$help->testtask->build = 'Build|The build to test.'; +$help->testtask->name = 'Task name'; +$help->testtask->begin = 'Begin date'; +$help->testtask->end = 'End date'; +$help->testtask->desc = 'Task description'; +$help->testtask->status = 'Test task status'; +$help->testtask->assignedto = 'Assigned to|Who in charge of the testcase.'; +$help->testtask->linkversion = 'Link version|The version of the case to run.'; +$help->testtask->lastrun = 'Last runed by'; +$help->testtask->lastresult = 'Last result'; + +$help->todo->date = 'Date|'; +$help->todo->begin = 'Begin time|'; +$help->todo->end = 'End time|'; +$help->todo->type = 'Type|Custom, bug or task. You can link a task or bug assigned to you and add it as a todo.'; +$help->todo->pri = 'Priority'; +$help->todo->name = 'Todo name'; +$help->todo->status = 'Status'; +$help->todo->desc = 'Description'; +$help->todo->private = 'Is private|If set as private, nobody can see it'; + +$help->user->account = 'Account|Should contain letters, underline or numbers, three above.'; +$help->user->password = 'Password|Six above'; +$help->user->password2 = 'Repeat password to confirm'; +$help->user->realname = 'Realname'; +$help->user->email = 'Email|Very important field, it is the default notify tool in zentao..'; +$help->user->join = 'Join date|The date the employee join the company.'; +$help->user->visits = 'Visit counts'; +$help->user->ip = 'Last login ip'; +$help->user->last = 'Last login time'; + +$help->my->date = 'Select date|Select the data of todoes.'; +$help->user->date = 'Select date|Select the data of todoes.'; + +$help->doc->product = 'Product'; +$help->doc->project = 'Project'; +$help->doc->library = 'Library'; +$help->doc->module = 'Doc category'; +$help->doc->type = 'Doc type'; +$help->doc->title = 'Doc title'; +$help->doc->digest = 'Doc digest'; +$help->doc->url = 'The url'; diff --git a/trunk/module/help/lang/field.zh-cn.php b/trunk/module/help/lang/field.zh-cn.php new file mode 100644 index 0000000000..6d68561b3f --- /dev/null +++ b/trunk/module/help/lang/field.zh-cn.php @@ -0,0 +1,211 @@ +file->labels = '附件名称|可以自己设定文件的标题,如果不设定,则取附件原来的文件名作为标题。'; + +$help->bug->product = '所属产品|设定bug所属的产品。'; +$help->bug->module = '所属模块|设定Bug所属的模块。提示:Bug系统的模块和产品视图里面的模块是分开维护的。'; +$help->bug->project = '所属项目|设定Bug所属的项目。'; +$help->bug->story = '相关需求|设定Bug和哪个需求相关。'; +$help->bug->task = '相关任务|设定Bug和哪个任务相关。'; +$help->bug->title = 'bug标题|Bug的标题,应用清晰明了的描述,是非常重要的信息。'; +$help->bug->severity = '严重程度|Bug的严重程度,但和优先级不能等同,一般按照1-4,严重程度递减,但团队也可以自己设定。'; +$help->bug->pri = '优先级|Bug的优先级,用来决定Bug处理的优先级别,一般应当由产品人员或项目经理指定。'; +$help->bug->type = 'bug类型|仔细设置Bug的类型,对后面的统计帮助可以很有好处。可以看出产品的缺陷主要集中在什么地方。'; +$help->bug->os = '操作系统|产生Bug的操作系统。'; +$help->bug->browser = '浏览器|产生Bug的浏览器,一般B/S架构的产品都会面临不同浏览器的兼容问题,应当仔细设置,方便排查原因。'; +$help->bug->steps = '重现步骤|Bug的重现步骤,也是bug最为重要的信息,一定要将完整的重现步骤写清楚。如果有抓图,一定提供抓图。提示:编辑器里面可以直接上传图片。'; +$help->bug->status = 'bug状态|Bug当前所处的状态,禅道里面Bug总共为Active, Resolved, Closed三个状态。'; +$help->bug->mailto = '抄送给|当前bug所有的操作都会抄送给该列表,抄送给可以是多个人,输入用户名(非真实姓名)进行选择。'; +$help->bug->openedby = '由谁创建|Bug的创建者。'; +$help->bug->openedbuild = '影响版本|Bug影响的版本,可以选择多个。提示:如果版本为空,需要到相应的项目中创建Build。'; +$help->bug->assignedto = '指派给|当前bug应当由谁处理,如果不清楚,留空。团队应当指定由谁来负责处理指派为空的Bug。'; +$help->bug->resolvedby = '解决者|Bug的解决者。'; +$help->bug->resolution = '解决方案|Bug的解决方案,开发人员应当认真选择该字段,对后面的统计也非常有帮助。'; +$help->bug->resolvedbuild = '解决版本|Bug的解决版本。'; +$help->bug->closedby = '由谁关闭|bug是由谁来关闭的。'; +$help->bug->closeddate = '关闭日期|Bug的关闭日期。'; +$help->bug->duplicatebug = '重复bug|重复的Bug,当解决方案为重复的时,必须指定重复的Bug的ID'; +$help->bug->linkbug = '相关bug|相关的Bug,和重复的bug不同,可以填写多个Bug的Id,中间使用英文逗号隔开'; +$help->bug->case = '相关用例|bug的相关用例'; +$help->bug->keywords = '关键词|可以灵活运用这个字段,方便进行查询检索。'; + +$help->build->product = '产品|所属的产品。'; +$help->build->project = '项目|所属的项目'; +$help->build->name = '名称编号|Build的编号,团队应当建立自己的配置管理规范,比如zentaopms.1.5.beta1.20110315'; +$help->build->date = 'build日期|打包的日期。'; +$help->build->builder = '构建者|谁创建的包'; +$help->build->scmpath = '源代码地址|如果有源代码管理系统,比如svn,可以填写完整的build的地址(tag的地址)'; +$help->build->filepath = '存储地址|或者是编译好之后的软件包的存储地址。'; +$help->build->desc = '描述|描述这个build完成了哪些功能,解决了哪些bug等,对测试的建议等。'; + +$help->company->name = '公司名称'; +$help->company->phone = '联系电话'; +$help->company->fax = '传真'; +$help->company->address = '通讯地址'; +$help->company->zipcode = '邮政编码'; +$help->company->website = '公司网站|即公司的官网地址,要写完整的http://,会出现在页面最上方,作为一个快捷链接。'; +$help->company->backyard = '公司内网|即公司的内网地址,也要写完整的http://,会出现页面的最上方,作为一个快捷链接。'; +$help->company->pms = 'pms网站|也就是禅道系统的域名,这个一般不需要修改,如果修改,不要填写http,只填域名部分。'; +$help->company->guest = '匿名登录|是否运行匿名登录。如果允许的话,需要在组织视图中,添加一个guest分组,并为该组分配相应的权限。'; + +$help->convert->dbhost = '数据库服务器|来源系统所在的服务器。'; +$help->convert->dbport = '服务器端口|来源系统数据库运行的端口号,一般是3306。'; +$help->convert->dbuser = '数据库用户名|访问来源系统数据库的帐号。'; +$help->convert->dbpassword = '数据库密码|访问来源系统数据库的密码。'; +$help->convert->dbname = '使用的库|数据库的名字。'; +$help->convert->dbprefix = '表前缀|表的前缀。'; +$help->convert->installpath= '安装的根目录|来源系统所在的根目录,一般用来拷贝附件。如果来源系统和禅道不在同一台机器,请先将其拷贝到一台机器。'; + +$help->dept->depts = '下级部门|每次最多可以设置五个下级部门。'; +$help->dept->orders = '部门排序|输入数字,可以对部门进行排序,建议数字有一定的间隔,方便中间插入新的数据。'; + +$help->group->name = '分组名称|如果设置公司允许匿名访问,需要建立一个guest的分组,然后为其分配相应的权限。'; +$help->group->desc = '分组描述'; + +$help->install->webroot = 'pms所在的目录|安装的时候,程序会自动设置,一般无需修改。如果后面目录有移动,需要修改config/my.php中的webRoot变量。'; +$help->install->requesttype = 'url方式|即通过什么方式来访问pms。GET方式是最通用的,静态url地址方式需要有url重写的功能。如果你不确定,建议使用GET方式。'; +$help->install->defaultlang = '默认语言|可以设定系统默认访问的语言。'; +$help->install->dbhost = '数据库服务器|数据库所在的服务器,一般来讲为localhost,或者试试127.0.0.1'; +$help->install->dbport = '服务器端口|一般为3306。'; +$help->install->dbuser = '数据库用户名|一般默认安装为root'; +$help->install->dbpassword = '数据库密码|一般默认安装密码为空的。'; +$help->install->dbname = 'pms使用的库|禅道使用的库名。'; +$help->install->dbprefix = '建表使用的前缀|使用前缀,避免和其他的系统表名冲突。'; +$help->install->cleardb = '清空现有数据|如果库里面已经有过禅道的表,可以选择该选项,重新安装。'; +$help->install->company = '公司名称|公司的名称'; +$help->install->pms = 'pms地址|pms的地址,一般安装的时候系统会自动设好,不需要修改。'; +$help->install->account = '管理员帐号|管理员的帐号,该管理员为超级管理员,拥有所有的权限。'; +$help->install->password = '管理员密码|管理员的密码。请尽量复杂。'; + +$help->product->name = '产品名称'; +$help->product->code = '产品代号|作为团队内部对某一个产品的简短称呼。'; +$help->product->po = '产品负责人|当前产品的负责人,负责维护需求,解释需求,制定计划等。'; +$help->product->rm = '发布负责人|由谁来负责创建各种版本,对外发布这些工作。'; +$help->product->qm = '测试负责人|该产品的测试负责人,负责协调测试资源,管理测试任务等工作。'; +$help->product->status = '状态|产品的状态,目前暂时分为正常和已结束。所谓已结束,就是产品不再有任何的行为,无论是开发,维护,还是销售。'; +$help->product->desc = '产品描述'; + +$help->productplan->product = '产品'; +$help->productplan->title = '名称|计划的名称,一般不要过长,简短好记,方便团队内部传递信息。'; +$help->productplan->desc = '描述|可以对计划进行较详细的描述。'; +$help->productplan->begin = '开始日期|计划开始的日期。'; +$help->productplan->end = '结束日期|计划结束的日期。'; + +$help->project->name = '项目名称'; +$help->project->code = '项目代号|项目的代码,作为团队内部对某一个项目的简短称呼。'; +$help->project->begin = '开始日期|项目一般应该有明确的起止时间。'; +$help->project->end = '结束日期|对于scrum而言,一般不宜超过30天。'; +$help->project->team = '团队名称|团队内部可以自己选择自己喜欢的名称。'; +$help->project->status = '项目状态|只有状态为进行中的项目,其燃烧图才会在首页出现。'; +$help->project->desc = '项目描述|项目的描述。'; +$help->project->goal = '项目目标|项目所要取得的目标。'; +$help->project->updateburn = '更新燃尽图|
      1. 通过手工点击“更新燃尽图”来进行更新。
      2. 通过计划任务来进行更新。 + 详情请查看《如何更新燃尽图》'; + +$help->release->product = '产品'; +$help->release->build = 'build|所对应的build。build是在项目视图中,在某一个项目中创建。'; +$help->release->name = '发布名称|产品对外发布的名称。比如禅道1.0正式版本'; +$help->release->date = '发布日期'; +$help->release->desc = '描述|可以描述本次发布的修改记录,功能改进,下载链接等信息。'; + +$help->story->product = '所属产品'; +$help->story->module = '所属模块|属于哪个模块。做好功能模块的划分,对维护需求来讲很重要。'; +$help->story->plan = '产品计划|属于哪个计划,通过计划,可以对产品进行宏观的把握。'; +$help->story->title = '需求名称|很重要的信息,应该用清晰明了的语言描述。'; +$help->story->spec = '需求描述|需求的描述,尽量按照禅道给出的模板来描述。'; +$help->story->verify = '验收标准|需求验收的标准'; +$help->story->pri = '优先级|很重要的字段,在关联需求到项目的时候,需要按照优先级进行排序。'; +$help->story->estimate = '预计工时|产品人员在建立需求时,应对该需求所需要花费的时间进行大致估计,或者是团队成员一起达成一致。该字段在确定项目所做的需求时,其参考作用。'; +$help->story->status = '当前状态|需求当前的状态,其中只有处于激活状态的需求才可能关联到项目,进行任务的分解。'; +$help->story->stage = '所处阶段|当需求处在激活状态之后,描述需求当前所处的阶段。一般不需要人手工维护,禅道系统会自动判断。'; +$help->story->mailto = '抄送给|跟这个需求相关的人员,可以通过email的形式抄送给他们。提示:请输入用户名进行选择。'; +$help->story->openedby = '由谁创建|需求的创建者,一般来讲,也是需求的负责人。'; +$help->story->openeddate = '创建日期'; +$help->story->assignedto = '指派给|需求当前需要处理的人,一般用来走评审流程。'; +$help->story->assigneddate = '指派日期'; +$help->story->closedby = '由谁关闭|需求由谁关闭。'; +$help->story->closeddate = '关闭日期'; +$help->story->closedreason = '关闭原因|当一个需求被关闭之后,需要给一个关闭的原因。'; +$help->story->rejectedreason = '拒绝原因|如果需求评审没有通过,需要给一个拒绝的原因。'; +$help->story->reviewedby = '由谁评审|需求是由谁来评审的。可能是多个人。比如团队开会,共同讨论某一个需求。'; +$help->story->revieweddate = '评审时间'; +$help->story->comment = '备注'; +$help->story->linkstories = '相关需求|相关的需求,可以是多个需求的id,用逗号隔开。'; +$help->story->childstories = '细分需求|该需求太大,细分成若干个小需求进行跟踪。如果需求评审结果为已细分,需要给出细分之后的需求id。'; +$help->story->duplicatestory = '重复需求'; +$help->story->reviewresult = '评审结果'; +$help->story->keywords = '关键词|可以通过关键词更好的组织维护需求。'; +$help->story->neednotreview = '不需要评审|如果团队没有需求评审流程,比如就只有一个产品人员,可以将这个选项勾上。'; + +$help->task->project = '所属项目'; +$help->task->story = '相关需求|任务所对应的需求。'; +$help->task->name = '任务名称|很重要的信息,清晰明了的语言描述清楚。'; +$help->task->type = '任务类型|任务的类型,可以用来区分不同的任务。'; +$help->task->pri = '优先级|很重要的字段,用来对需求进行排序。'; +$help->task->assignedto = '指派给|任务的负责人。'; +$help->task->estimate = '最初预计|对该任务最初的预计。单位为工时'; +$help->task->left = '预计剩余|预计该任务完成还需要多少工时,非常重要的字段,需要项目的每一位成员每天下班前来更新该字段。项目的燃烧图也是根据这个字段计算出来的。'; +$help->task->consumed = '已经消耗|已经消耗的时间。需要注意的是,已经消耗 + 预计剩余 和最初的预计不是必然相等的,很多时候往往是不等的。'; +$help->task->deadline = '截止日期|任务的截至日期,如果逾期,会有警告显示。'; +$help->task->status = '任务状态|任务当前的状态。'; +$help->task->desc = '任务描述|任务的详细描述。'; + +$help->testcase->product = '所属产品'; +$help->testcase->module = '所属模块|提示:测试用例的模块也是和产品的单独分开的,需要单独维护。'; +$help->testcase->story = '相关需求|该用例对应到哪个需求,非常重要。'; +$help->testcase->title = '用例标题|非常重要的字段,一定要描述清楚。尤其是在用例很多切相似的情况下。'; +$help->testcase->pri = '优先级'; +$help->testcase->type = '用例类型|一般来讲是功能测试,但如果测试充分,应当撰写其他类型的测试用例。'; +$help->testcase->status = '用例状态'; +$help->testcase->steps = '用例步骤|非常重要。不要将两个用例混为一个用例。一个用例的步骤就是单纯的一个用例,也就是说用例的细分越细越好。'; +$help->testcase->openedby = '由谁创建'; +$help->testcase->openeddate = '创建日期'; +$help->testcase->result = '测试结果|用例执行的结果'; +$help->testcase->real = '实际情况|用例执行的实际输出'; +$help->testcase->keywords = '关键词'; +$help->testcase->linkcase = '相关用例'; +$help->testcase->stage = '适用阶段|该用例适用在什么阶段。'; + +$help->testtask->product = '所属产品|测试的是哪个产品。'; +$help->testtask->project = '所属项目|哪个项目中产生的测试任务'; +$help->testtask->build = 'build|需要测试哪个Build,非常重要。测试人员测试的都应该是固定的东西,不应该是时刻变化的系统。如果没有build,需要到项目视图加以创建。'; +$help->testtask->name = '任务名称|测试任务的名称。'; +$help->testtask->begin = '开始日期'; +$help->testtask->end = '结束日期'; +$help->testtask->desc = '任务描述'; +$help->testtask->status = '当前状态'; +$help->testtask->assignedto = '指派给|一个测试任务可能要执行很多个测试用例,可以将测试用例进行细分,某某跑几个用例,另外一个人跑其他的用例。'; +$help->testtask->linkversion= '关联(版本)|用例的版本,一般来讲应当执行最新的版本。'; +$help->testtask->lastrun = '最后执行'; +$help->testtask->lastresult = '最终结果'; + +$help->todo->date = '日期|执行的日期,可以选择暂不指定,只是做一个记录,后面再来安排。'; +$help->todo->begin = '开始时间|预计开始的时间'; +$help->todo->end = '结束时间|预计结束的时间'; +$help->todo->type = '类型|类型,目前分为自定义,bug和任务三种。后两者会自动将你所负责的任务或者bug列出,然后选择是否今天处理。'; +$help->todo->pri = '优先级|很重要,对事情一定要分优先级。'; +$help->todo->name = '名称'; +$help->todo->status = '状态|当前的状态。'; +$help->todo->desc = '描述|代办事宜的描述。'; +$help->todo->private = '私人事务|私人事务,别人不会看到具体的内容。'; + +$help->user->account = '用户名|用来登录使用的,英文,数字,下划线的组合,三位以上。'; +$help->user->password = '密码|六位以上。'; +$help->user->password2 = '请重复密码|确认密码。'; +$help->user->realname = '真实姓名'; +$help->user->email = '邮箱|用来联系用的邮箱,很重要。禅道里面的很多提醒都是通过邮箱来做的。'; +$help->user->join = '加入日期|也就是员工的入职日期。'; +$help->user->visits = '访问次数'; +$help->user->ip = '最后ip'; +$help->user->last = '最后登录时间'; + +$help->my->date = '选择日期|选择要查看的todo的日期'; +$help->user->date = '选择日期|选择要查看的todo的日期'; + +$help->doc->product = '所属产品'; +$help->doc->project = '所属项目'; +$help->doc->library = '所属文档库'; +$help->doc->module = '文档分类'; +$help->doc->type = '文档类型'; +$help->doc->title = '文档标题'; +$help->doc->digest = '文档摘要'; +$help->doc->url = '相应的链接地址'; diff --git a/trunk/module/help/lang/field.zh-tw.php b/trunk/module/help/lang/field.zh-tw.php new file mode 100644 index 0000000000..a8ff056736 --- /dev/null +++ b/trunk/module/help/lang/field.zh-tw.php @@ -0,0 +1,211 @@ +file->labels = '附件名稱|可以自己設定檔案的標題,如果不設定,則取附件原來的檔案名作為標題。'; + +$help->bug->product = '所屬產品|設定bug所屬的產品。'; +$help->bug->module = '所屬模組|設定Bug所屬的模組。提示:Bug系統的模組和產品視圖裡面的模組是分開維護的。'; +$help->bug->project = '所屬項目|設定Bug所屬的項目。'; +$help->bug->story = '相關需求|設定Bug和哪個需求相關。'; +$help->bug->task = '相關任務|設定Bug和哪個任務相關。'; +$help->bug->title = 'bug標題|Bug的標題,應用清晰明了的描述,是非常重要的信息。'; +$help->bug->severity = '嚴重程度|Bug的嚴重程度,但和優先順序不能等同,一般按照1-4,嚴重程度遞減,但團隊也可以自己設定。'; +$help->bug->pri = '優先順序|Bug的優先順序,用來決定Bug處理的優先順序別,一般應當由產品人員或項目經理指定。'; +$help->bug->type = 'bug類型|仔細設置Bug的類型,對後面的統計幫助可以很有好處。可以看出產品的缺陷主要集中在什麼地方。'; +$help->bug->os = '操作系統|產生Bug的操作系統。'; +$help->bug->browser = '瀏覽器|產生Bug的瀏覽器,一般B/S架構的產品都會面臨不同瀏覽器的兼容問題,應當仔細設置,方便排查原因。'; +$help->bug->steps = '重現步驟|Bug的重現步驟,也是bug最為重要的信息,一定要將完整的重現步驟寫清楚。如果有抓圖,一定提供抓圖。提示:編輯器裡面可以直接上傳圖片。'; +$help->bug->status = 'bug狀態|Bug當前所處的狀態,禪道里面Bug總共為Active, Resolved, Closed三個狀態。'; +$help->bug->mailto = '抄送給|當前bug所有的操作都會抄送給該列表,抄送給可以是多個人,輸入用戶名(非真實姓名)進行選擇。'; +$help->bug->openedby = '由誰創建|Bug的創建者。'; +$help->bug->openedbuild = '影響版本|Bug影響的版本,可以選擇多個。提示:如果版本為空,需要到相應的項目中創建Build。'; +$help->bug->assignedto = '指派給|當前bug應當由誰處理,如果不清楚,留空。團隊應當指定由誰來負責處理指派為空的Bug。'; +$help->bug->resolvedby = '解決者|Bug的解決者。'; +$help->bug->resolution = '解決方案|Bug的解決方案,開發人員應當認真選擇該欄位,對後面的統計也非常有幫助。'; +$help->bug->resolvedbuild = '解決版本|Bug的解決版本。'; +$help->bug->closedby = '由誰關閉|bug是由誰來關閉的。'; +$help->bug->closeddate = '關閉日期|Bug的關閉日期。'; +$help->bug->duplicatebug = '重複bug|重複的Bug,當解決方案為重複的時,必須指定重複的Bug的ID'; +$help->bug->linkbug = '相關bug|相關的Bug,和重複的bug不同,可以填寫多個Bug的Id,中間使用英文逗號隔開'; +$help->bug->case = '相關用例|bug的相關用例'; +$help->bug->keywords = '關鍵詞|可以靈活運用這個欄位,方便進行查詢檢索。'; + +$help->build->product = '產品|所屬的產品。'; +$help->build->project = '項目|所屬的項目'; +$help->build->name = '名稱編號|Build的編號,團隊應當建立自己的配置管理規範,比如zentaopms.1.5.beta1.20110315'; +$help->build->date = 'build日期|打包的日期。'; +$help->build->builder = '構建者|誰創建的包'; +$help->build->scmpath = '原始碼地址|如果有原始碼管理系統,比如svn,可以填寫完整的build的地址(tag的地址)'; +$help->build->filepath = '存儲地址|或者是編譯好之後的軟件包的存儲地址。'; +$help->build->desc = '描述|描述這個build完成了哪些功能,解決了哪些bug等,對測試的建議等。'; + +$help->company->name = '公司名稱'; +$help->company->phone = '聯繫電話'; +$help->company->fax = '傳真'; +$help->company->address = '通訊地址'; +$help->company->zipcode = '郵政編碼'; +$help->company->website = '公司網站|即公司的官網地址,要寫完整的http://,會出現在頁面最上方,作為一個快捷連結。'; +$help->company->backyard = '公司內網|即公司的內網地址,也要寫完整的http://,會出現頁面的最上方,作為一個快捷連結。'; +$help->company->pms = 'pms網站|也就是禪道系統的域名,這個一般不需要修改,如果修改,不要填寫http,只填域名部分。'; +$help->company->guest = '匿名登錄|是否運行匿名登錄。如果允許的話,需要在組織視圖中,添加一個guest分組,併為該組分配相應的權限。'; + +$help->convert->dbhost = '資料庫伺服器|來源系統所在的伺服器。'; +$help->convert->dbport = '伺服器連接埠|來源系統資料庫運行的連接埠號,一般是3306。'; +$help->convert->dbuser = '資料庫用戶名|訪問來源系統資料庫的帳號。'; +$help->convert->dbpassword = '資料庫密碼|訪問來源系統資料庫的密碼。'; +$help->convert->dbname = '使用的庫|資料庫的名字。'; +$help->convert->dbprefix = '表首碼|表的首碼。'; +$help->convert->installpath= '安裝的根目錄|來源系統所在的根目錄,一般用來拷貝附件。如果來源系統和禪道不在同一台機器,請先將其拷貝到一台機器。'; + +$help->dept->depts = '下級部門|每次最多可以設置五個下級部門。'; +$help->dept->orders = '部門排序|輸入數字,可以對部門進行排序,建議數字有一定的間隔,方便中間插入新的數據。'; + +$help->group->name = '分組名稱|如果設置公司允許匿名訪問,需要建立一個guest的分組,然後為其分配相應的權限。'; +$help->group->desc = '分組描述'; + +$help->install->webroot = 'pms所在的目錄|安裝的時候,程序會自動設置,一般無需修改。如果後面目錄有移動,需要修改config/my.php中的webRoot變數。'; +$help->install->requesttype = 'url方式|即通過什麼方式來訪問pms。GET方式是最通用的,靜態url地址方式需要有url重寫的功能。如果你不確定,建議使用GET方式。'; +$help->install->defaultlang = '預設語言|可以設定系統預設訪問的語言。'; +$help->install->dbhost = '資料庫伺服器|資料庫所在的伺服器,一般來講為localhost,或者試試127.0.0.1'; +$help->install->dbport = '伺服器連接埠|一般為3306。'; +$help->install->dbuser = '資料庫用戶名|一般預設安裝為root'; +$help->install->dbpassword = '資料庫密碼|一般預設安裝密碼為空的。'; +$help->install->dbname = 'pms使用的庫|禪道使用的庫名。'; +$help->install->dbprefix = '建表使用的首碼|使用首碼,避免和其他的系統表名衝突。'; +$help->install->cleardb = '清空現有數據|如果庫裡面已經有過禪道的表,可以選擇該選項,重新安裝。'; +$help->install->company = '公司名稱|公司的名稱'; +$help->install->pms = 'pms地址|pms的地址,一般安裝的時候系統會自動設好,不需要修改。'; +$help->install->account = '管理員帳號|管理員的帳號,該管理員為超級管理員,擁有所有的權限。'; +$help->install->password = '管理員密碼|管理員的密碼。請儘量複雜。'; + +$help->product->name = '產品名稱'; +$help->product->code = '產品代號|作為團隊內部對某一個產品的簡短稱呼。'; +$help->product->po = '產品負責人|當前產品的負責人,負責維護需求,解釋需求,制定計劃等。'; +$help->product->rm = '發佈負責人|由誰來負責創建各種版本,對外發佈這些工作。'; +$help->product->qm = '測試負責人|該產品的測試負責人,負責協調測試資源,管理測試任務等工作。'; +$help->product->status = '狀態|產品的狀態,目前暫時分為正常和已結束。所謂已結束,就是產品不再有任何的行為,無論是開發,維護,還是銷售。'; +$help->product->desc = '產品描述'; + +$help->productplan->product = '產品'; +$help->productplan->title = '名稱|計劃的名稱,一般不要過長,簡短好記,方便團隊內部傳遞信息。'; +$help->productplan->desc = '描述|可以對計划進行較詳細的描述。'; +$help->productplan->begin = '開始日期|計劃開始的日期。'; +$help->productplan->end = '結束日期|計劃結束的日期。'; + +$help->project->name = '項目名稱'; +$help->project->code = '項目代號|項目的代碼,作為團隊內部對某一個項目的簡短稱呼。'; +$help->project->begin = '開始日期|項目一般應該有明確的起止時間。'; +$help->project->end = '結束日期|對於scrum而言,一般不宜超過30天。'; +$help->project->team = '團隊名稱|團隊內部可以自己選擇自己喜歡的名稱。'; +$help->project->status = '項目狀態|只有狀態為進行中的項目,其燃燒圖才會在首頁出現。'; +$help->project->desc = '項目描述|項目的描述。'; +$help->project->goal = '項目目標|項目所要取得的目標。'; +$help->project->updateburn = '更新燃盡圖|
      1. 通過手工點擊“更新燃盡圖”來進行更新。
      2. 通過計劃任務來進行更新。 + 詳情請查看《如何更新燃盡圖》'; + +$help->release->product = '產品'; +$help->release->build = 'build|所對應的build。build是在項目視圖中,在某一個項目中創建。'; +$help->release->name = '發佈名稱|產品對外發佈的名稱。比如禪道1.0正式版本'; +$help->release->date = '發佈日期'; +$help->release->desc = '描述|可以描述本次發佈的修改記錄,功能改進,下載連結等信息。'; + +$help->story->product = '所屬產品'; +$help->story->module = '所屬模組|屬於哪個模組。做好功能模組的劃分,對維護需求來講很重要。'; +$help->story->plan = '產品計劃|屬於哪個計劃,通過計劃,可以對產品進行宏觀的把握。'; +$help->story->title = '需求名稱|很重要的信息,應該用清晰明了的語言描述。'; +$help->story->spec = '需求描述|需求的描述,儘量按照禪道給出的模板來描述。'; +$help->story->verify = '驗收標準|需求驗收的標準'; +$help->story->pri = '優先順序|很重要的欄位,在關聯需求到項目的時候,需要按照優先順序進行排序。'; +$help->story->estimate = '預計工時|產品人員在建立需求時,應對該需求所需要花費的時間進行大致估計,或者是團隊成員一起達成一致。該欄位在確定項目所做的需求時,其參考作用。'; +$help->story->status = '當前狀態|需求當前的狀態,其中只有處于激活狀態的需求才可能關聯到項目,進行任務的分解。'; +$help->story->stage = '所處階段|當需求處在激活狀態之後,描述需求當前所處的階段。一般不需要人手工維護,禪道系統會自動判斷。'; +$help->story->mailto = '抄送給|跟這個需求相關的人員,可以通過email的形式抄送給他們。提示:請輸入用戶名進行選擇。'; +$help->story->openedby = '由誰創建|需求的創建者,一般來講,也是需求的負責人。'; +$help->story->openeddate = '創建日期'; +$help->story->assignedto = '指派給|需求當前需要處理的人,一般用來走評審流程。'; +$help->story->assigneddate = '指派日期'; +$help->story->closedby = '由誰關閉|需求由誰關閉。'; +$help->story->closeddate = '關閉日期'; +$help->story->closedreason = '關閉原因|當一個需求被關閉之後,需要給一個關閉的原因。'; +$help->story->rejectedreason = '拒絶原因|如果需求評審沒有通過,需要給一個拒絶的原因。'; +$help->story->reviewedby = '由誰評審|需求是由誰來評審的。可能是多個人。比如團隊開會,共同討論某一個需求。'; +$help->story->revieweddate = '評審時間'; +$help->story->comment = '備註'; +$help->story->linkstories = '相關需求|相關的需求,可以是多個需求的id,用逗號隔開。'; +$help->story->childstories = '細分需求|該需求太大,細分成若干個小需求進行跟蹤。如果需求評審結果為已細分,需要給出細分之後的需求id。'; +$help->story->duplicatestory = '重複需求'; +$help->story->reviewresult = '評審結果'; +$help->story->keywords = '關鍵詞|可以通過關鍵詞更好的組織維護需求。'; +$help->story->neednotreview = '不需要評審|如果團隊沒有需求評審流程,比如就只有一個產品人員,可以將這個選項勾上。'; + +$help->task->project = '所屬項目'; +$help->task->story = '相關需求|任務所對應的需求。'; +$help->task->name = '任務名稱|很重要的信息,清晰明了的語言描述清楚。'; +$help->task->type = '任務類型|任務的類型,可以用來區分不同的任務。'; +$help->task->pri = '優先順序|很重要的欄位,用來對需求進行排序。'; +$help->task->assignedto = '指派給|任務的負責人。'; +$help->task->estimate = '最初預計|對該任務最初的預計。單位為工時'; +$help->task->left = '預計剩餘|預計該任務完成還需要多少工時,非常重要的欄位,需要項目的每一位成員每天下班前來更新該欄位。項目的燃燒圖也是根據這個欄位計算出來的。'; +$help->task->consumed = '已經消耗|已經消耗的時間。需要注意的是,已經消耗 + 預計剩餘 和最初的預計不是必然相等的,很多時候往往是不等的。'; +$help->task->deadline = '截止日期|任務的截至日期,如果逾期,會有警告顯示。'; +$help->task->status = '任務狀態|任務當前的狀態。'; +$help->task->desc = '任務描述|任務的詳細描述。'; + +$help->testcase->product = '所屬產品'; +$help->testcase->module = '所屬模組|提示:測試用例的模組也是和產品的單獨分開的,需要單獨維護。'; +$help->testcase->story = '相關需求|該用例對應到哪個需求,非常重要。'; +$help->testcase->title = '用例標題|非常重要的欄位,一定要描述清楚。尤其是在用例很多切相似的情況下。'; +$help->testcase->pri = '優先順序'; +$help->testcase->type = '用例類型|一般來講是功能測試,但如果測試充分,應當撰寫其他類型的測試用例。'; +$help->testcase->status = '用例狀態'; +$help->testcase->steps = '用例步驟|非常重要。不要將兩個用例混為一個用例。一個用例的步驟就是單純的一個用例,也就是說用例的細分越細越好。'; +$help->testcase->openedby = '由誰創建'; +$help->testcase->openeddate = '創建日期'; +$help->testcase->result = '測試結果|用例執行的結果'; +$help->testcase->real = '實際情況|用例執行的實際輸出'; +$help->testcase->keywords = '關鍵詞'; +$help->testcase->linkcase = '相關用例'; +$help->testcase->stage = '適用階段|該用例適用在什麼階段。'; + +$help->testtask->product = '所屬產品|測試的是哪個產品。'; +$help->testtask->project = '所屬項目|哪個項目中產生的測試任務'; +$help->testtask->build = 'build|需要測試哪個Build,非常重要。測試人員測試的都應該是固定的東西,不應該是時刻變化的系統。如果沒有build,需要到項目視圖加以創建。'; +$help->testtask->name = '任務名稱|測試任務的名稱。'; +$help->testtask->begin = '開始日期'; +$help->testtask->end = '結束日期'; +$help->testtask->desc = '任務描述'; +$help->testtask->status = '當前狀態'; +$help->testtask->assignedto = '指派給|一個測試任務可能要執行很多個測試用例,可以將測試用例進行細分,某某跑幾個用例,另外一個人跑其他的用例。'; +$help->testtask->linkversion= '關聯(版本)|用例的版本,一般來講應當執行最新的版本。'; +$help->testtask->lastrun = '最後執行'; +$help->testtask->lastresult = '最終結果'; + +$help->todo->date = '日期|執行的日期,可以選擇暫不指定,只是做一個記錄,後面再來安排。'; +$help->todo->begin = '開始時間|預計開始的時間'; +$help->todo->end = '結束時間|預計結束的時間'; +$help->todo->type = '類型|類型,目前分為自定義,bug和任務三種。後兩者會自動將你所負責的任務或者bug列出,然後選擇是否今天處理。'; +$help->todo->pri = '優先順序|很重要,對事情一定要分優先順序。'; +$help->todo->name = '名稱'; +$help->todo->status = '狀態|當前的狀態。'; +$help->todo->desc = '描述|代辦事宜的描述。'; +$help->todo->private = '私人事務|私人事務,別人不會看到具體的內容。'; + +$help->user->account = '用戶名|用來登錄使用的,英文,數字,下劃線的組合,三位以上。'; +$help->user->password = '密碼|六位以上。'; +$help->user->password2 = '請重複密碼|確認密碼。'; +$help->user->realname = '真實姓名'; +$help->user->email = '郵箱|用來聯繫用的郵箱,很重要。禪道里面的很多提醒都是通過郵箱來做的。'; +$help->user->join = '加入日期|也就是員工的入職日期。'; +$help->user->visits = '訪問次數'; +$help->user->ip = '最後ip'; +$help->user->last = '最後登錄時間'; + +$help->my->date = '選擇日期|選擇要查看的todo的日期'; +$help->user->date = '選擇日期|選擇要查看的todo的日期'; + +$help->doc->product = '所屬產品'; +$help->doc->project = '所屬項目'; +$help->doc->library = '所屬文檔庫'; +$help->doc->module = '文檔分類'; +$help->doc->type = '文檔類型'; +$help->doc->title = '文檔標題'; +$help->doc->digest = '文檔摘要'; +$help->doc->url = '相應的連結地址'; diff --git a/trunk/module/help/lang/zh-cn.php b/trunk/module/help/lang/zh-cn.php new file mode 100644 index 0000000000..29436aa5dc --- /dev/null +++ b/trunk/module/help/lang/zh-cn.php @@ -0,0 +1,2 @@ +help->noHelpYet = '暂时还没有说明'; diff --git a/trunk/module/help/lang/zh-tw.php b/trunk/module/help/lang/zh-tw.php new file mode 100644 index 0000000000..19b49a114b --- /dev/null +++ b/trunk/module/help/lang/zh-tw.php @@ -0,0 +1,2 @@ +help->noHelpYet = '暫時還沒有說明'; diff --git a/trunk/module/help/model.php b/trunk/module/help/model.php new file mode 100644 index 0000000000..8947a3c3b1 --- /dev/null +++ b/trunk/module/help/model.php @@ -0,0 +1,15 @@ + + * @package help + * @version $Id$ + * @link http://www.zentao.net + */ +class helpModel extends model +{ +} + diff --git a/trunk/module/help/view/field.html.php b/trunk/module/help/view/field.html.php new file mode 100644 index 0000000000..c54f51fdf2 --- /dev/null +++ b/trunk/module/help/view/field.html.php @@ -0,0 +1,19 @@ + + * @package help + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + +
      ' . $fieldName . '' . ($fieldName ? $lang->arrow : '') . $fieldNote?>
      + + * @package ZenTaoPMS + * @version $Id$ + * @link http://www.zentao.net + */ +class index extends control +{ + /** + * Construct function, load project, product. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * The index page of whole zentao system. + * + * @access public + * @return void + */ + public function index() + { + $this->locate($this->createLink('my', 'index')); + } + + /** + * Just test the extension engine. + * + * @access public + * @return void + */ + public function testext() + { + echo $this->fetch('misc', 'getsid'); + } +} diff --git a/trunk/module/index/lang/en.php b/trunk/module/index/lang/en.php new file mode 100644 index 0000000000..67330d486f --- /dev/null +++ b/trunk/module/index/lang/en.php @@ -0,0 +1,3 @@ +index->common = 'Index'; +$lang->index->index = 'Index'; diff --git a/trunk/module/index/lang/zh-cn.php b/trunk/module/index/lang/zh-cn.php new file mode 100644 index 0000000000..24a9433269 --- /dev/null +++ b/trunk/module/index/lang/zh-cn.php @@ -0,0 +1,3 @@ +index->common = '首页'; +$lang->index->index = '首页'; diff --git a/trunk/module/index/lang/zh-tw.php b/trunk/module/index/lang/zh-tw.php new file mode 100644 index 0000000000..acd3f835f4 --- /dev/null +++ b/trunk/module/index/lang/zh-tw.php @@ -0,0 +1,3 @@ +index->common = '首頁'; +$lang->index->index = '首頁'; diff --git a/trunk/module/index/model.php b/trunk/module/index/model.php new file mode 100644 index 0000000000..2afcac9a23 --- /dev/null +++ b/trunk/module/index/model.php @@ -0,0 +1,15 @@ + + * @package index + * @version $Id$ + */ +?> + + * @package install + * @version $Id$ + * @link http://www.zentao.net + */ +class install extends control +{ + /** + * Construct function. + * + * @access public + * @return void + */ + public function __construct() + { + if(!defined('IN_INSTALL')) die(); + parent::__construct(); + $this->app->loadLang('user'); + $this->app->loadLang('admin'); + $this->config->webRoot = $this->install->getWebRoot(); + } + + /** + * Index page of install module. + * + * @access public + * @return void + */ + public function index() + { + if(!isset($this->config->installed) or !$this->config->installed) $this->session->set('installing', true); + + $this->view->header->title = $this->lang->install->welcome; + + if($release = $this->install->getLatestRelease()) $this->view->latestRelease = $release; + + $this->display(); + } + + /** + * Check the system. + * + * @access public + * @return void + */ + public function step1() + { + $this->view->header->title = $this->lang->install->checking; + $this->view->phpVersion = $this->install->getPhpVersion(); + $this->view->phpResult = $this->install->checkPHP(); + $this->view->pdoResult = $this->install->checkPDO(); + $this->view->pdoMySQLResult = $this->install->checkPDOMySQL(); + $this->view->jsonResult = $this->install->checkJSON(); + $this->view->tmpRootInfo = $this->install->getTmpRoot(); + $this->view->tmpRootResult = $this->install->checkTmpRoot(); + $this->view->dataRootInfo = $this->install->getDataRoot(); + $this->view->dataRootResult = $this->install->checkDataRoot(); + $this->view->iniInfo = $this->install->getIniInfo(); + $this->display(); + } + + /** + * Set configs. + * + * @access public + * @return void + */ + public function step2() + { + $this->view->header->title = $this->lang->install->setConfig; + $this->display(); + } + + /** + * Create the config file. + * + * @access public + * @return void + */ + public function step3() + { + if(!empty($_POST)) + { + $return = $this->install->checkConfig(); + if($return->result == 'ok') + { + $this->view = (object)$_POST; + $this->view->app = $this->app; + $this->view->lang = $this->lang; + $this->view->config = $this->config; + $this->view->domain = $this->server->HTTP_HOST; + $this->view->header->title = $this->lang->install->saveConfig; + $this->display(); + } + else + { + $this->view->header->title = $this->lang->install->saveConfig; + $this->view->error = $return->error; + $this->display(); + } + } + else + { + $this->locate($this->createLink('install')); + } + } + + /** + * Create company, admin. + * + * @access public + * @return void + */ + public function step4() + { + if(!empty($_POST)) + { + $this->install->grantPriv(); + if(dao::isError()) die(js::error(dao::getError())); + + if($this->post->importDemoData) $this->install->importDemoData(); + if(dao::isError()) echo js::alert($this->lang->install->errorImportDemoData); + + $this->loadModel('setting')->updateVersion($this->config->version); + $this->setting->setSN(); + die(js::locate(inlink('step5'), 'parent')); + } + + $this->view->header->title = $this->lang->install->getPriv; + if(!isset($this->config->installed) or !$this->config->installed) + { + $this->view->error = $this->lang->install->errorNotSaveConfig; + $this->display(); + } + else + { + $this->view->pmsDomain = $this->server->HTTP_HOST; + $this->display(); + } + } + + /** + * Join zentao community or login pms. + * + * @access public + * @return void + */ + public function step5() + { + $this->display(); + unset($_SESSION['installing']); + session_destroy(); + } +} diff --git a/trunk/module/install/css/common.css b/trunk/module/install/css/common.css new file mode 100644 index 0000000000..bdf3cdf874 --- /dev/null +++ b/trunk/module/install/css/common.css @@ -0,0 +1,5 @@ +body{background:white; margin-top:50px} +caption, th, td {padding:10px; font-size:16px} +.ok{background:green; color:white} +.fail{background:red; color:white} +th{text-align:right} diff --git a/trunk/module/install/lang/en.php b/trunk/module/install/lang/en.php new file mode 100644 index 0000000000..7adb1c502b --- /dev/null +++ b/trunk/module/install/lang/en.php @@ -0,0 +1,112 @@ + + * @package install + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->install->common = 'Install'; +$lang->install->next = 'Next'; +$lang->install->pre = 'Back'; +$lang->install->reload = 'Reload'; +$lang->install->error = 'Error '; + +$lang->install->start = 'Start install'; +$lang->install->keepInstalling = 'Keep install this version'; +$lang->install->seeLatestRelease = 'See the latest release.'; +$lang->install->welcome = 'Welcome to use ZenTaoPMS.'; +$lang->install->desc = <<Nature EasySoft Network Tecnology Co.ltd, QingDao, China。 +The official website of ZenTaoPMS is http://en.zentao.net +twitter:zentaopms + +The version of current release is %s。 +EOT; + + + +$lang->install->newReleased= "Notice:There is a new version %s, released on %s。"; +$lang->install->choice = 'You can '; +$lang->install->checking = 'System checking'; +$lang->install->ok = 'OK(√)'; +$lang->install->fail = 'Failed(×)'; +$lang->install->loaded = 'Loaded'; +$lang->install->unloaded = 'Not loaded'; +$lang->install->exists = 'Exists '; +$lang->install->notExists = 'Not exists '; +$lang->install->writable = 'Writable '; +$lang->install->notWritable= 'Not writable '; +$lang->install->phpINI = 'PHP ini file'; +$lang->install->checkItem = 'Items'; +$lang->install->current = 'Current'; +$lang->install->result = 'Result'; +$lang->install->action = 'How to fix'; + +$lang->install->phpVersion = 'PHP version'; +$lang->install->phpFail = 'Must > 5.2.0'; + +$lang->install->pdo = 'PDO extension'; +$lang->install->pdoFail = 'Edit the php.ini file to load PDO extsion.'; +$lang->install->pdoMySQL = 'PDO_MySQL extension'; +$lang->install->pdoMySQLFail = 'Edit the php.ini file to load PDO_MySQL extsion.'; +$lang->install->json = 'JSON extension'; +$lang->install->jsonFail = 'Edit the php.ini file to load JSON extension'; +$lang->install->tmpRoot = 'Temp directory'; +$lang->install->dataRoot = 'Upload directory.'; +$lang->install->mkdir = '

      Should creat the directory %s。
      Under linux, can try
      mkdir -p %s

      '; +$lang->install->chmod = 'Should change the permission of "%s".
      Under linux, can try
      chmod o=rwx -R %s'; + +$lang->install->settingDB = 'Set database'; +$lang->install->webRoot = 'ZenTaoPMS path'; +$lang->install->requestType = 'URL type'; +$lang->install->defaultLang = 'Default Language'; +$lang->install->dbHost = 'Database host'; +$lang->install->dbHostNote = 'If localhost can not connect, try 127.0.0.1'; +$lang->install->dbPort = 'Host port'; +$lang->install->dbUser = 'Database user'; +$lang->install->dbPassword = 'Database password'; +$lang->install->dbName = 'Database name'; +$lang->install->dbPrefix = 'Table prefix'; +$lang->install->createDB = 'Auto create database'; +$lang->install->clearDB = 'Clear data if database exists.'; +$lang->install->importDemoData = 'Import demo data if database exists.'; + +$lang->install->requestTypes['GET'] = 'GET'; +$lang->install->requestTypes['PATH_INFO'] = 'PATH_INFO'; + +$lang->install->errorConnectDB = 'Database connect failed.'; +$lang->install->errorCreateDB = 'Database create failed.'; +$lang->install->errorTableExists = 'The same tables alread exists, to continue install, go back and check the clear db box.'; +$lang->install->errorCreateTable = 'Table create failed.'; +$lang->install->errorImportDemoData = 'Import demo data.'; + +$lang->install->setConfig = 'Create config file'; +$lang->install->key = 'Item'; +$lang->install->value = 'Value'; +$lang->install->saveConfig = 'Save config'; +$lang->install->save2File = '
      Try to save the config auto, but failed.
      Copy the text of the textareaand save to " %s ".'; +$lang->install->saved2File = 'The config file has saved to "%s ".'; +$lang->install->errorNotSaveConfig = "Hasn't save the config file. "; + +$lang->install->getPriv = 'Set admin'; +$lang->install->company = 'Company name'; +$lang->install->pms = 'ZenTaoPMS domain'; +$lang->install->pmsNote = 'The domain name or ip address of ZenTaoPMS, no http://'; +$lang->install->account = 'Administrator'; +$lang->install->password = 'Admin password'; +$lang->install->errorEmptyPassword = "Can't be empty"; + +$lang->install->success = "Success installed"; + +$lang->install->joinZentao = <<Please remove install.php in time。Now you can login ZenTaoPMS, create groups and grant priviledges. +Tips: For you get zentao news in time, please register Zetao community(www.zentao.net). +EOT; diff --git a/trunk/module/install/lang/zh-cn.php b/trunk/module/install/lang/zh-cn.php new file mode 100644 index 0000000000..7990f48424 --- /dev/null +++ b/trunk/module/install/lang/zh-cn.php @@ -0,0 +1,112 @@ + + * @package install + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->install->common = '安装'; +$lang->install->next = '下一步'; +$lang->install->pre = '返回'; +$lang->install->reload = '刷新'; +$lang->install->error = '错误 '; + +$lang->install->start = '开始安装'; +$lang->install->keepInstalling = '继续安装当前版本'; +$lang->install->seeLatestRelease = '看看最新的版本'; +$lang->install->welcome = '欢迎使用禅道项目管理软件!'; +$lang->install->desc = <<青岛易软天创网络科技有限公司开发。 +官方网站:http://www.zentao.net +技术支持: http://www.zentao.net/ask/ +新浪微博:t.sina.com.cn/zentaopms +腾讯微博:t.qq.com/zentaopms + +您现在正在安装的版本是 %s。 +EOT; + +$lang->install->newReleased= "提示:官网网站已有最新版本%s, 发布日期于 %s。"; +$lang->install->choice = '您可以选择:'; +$lang->install->checking = '系统检查'; +$lang->install->ok = '检查通过(√)'; +$lang->install->fail = '检查失败(×)'; +$lang->install->loaded = '已加载'; +$lang->install->unloaded = '未加载'; +$lang->install->exists = '目录存在 '; +$lang->install->notExists = '目录不存在 '; +$lang->install->writable = '目录可写 '; +$lang->install->notWritable= '目录不可写 '; +$lang->install->phpINI = 'PHP配置文件'; +$lang->install->checkItem = '检查项'; +$lang->install->current = '当前配置'; +$lang->install->result = '检查结果'; +$lang->install->action = '如何修改'; + +$lang->install->phpVersion = 'PHP版本'; +$lang->install->phpFail = 'PHP版本必须大于5.2.0'; + +$lang->install->pdo = 'PDO扩展'; +$lang->install->pdoFail = '修改PHP配置文件,加载PDO扩展。'; +$lang->install->pdoMySQL = 'PDO_MySQL扩展'; +$lang->install->pdoMySQLFail = '修改PHP配置文件,加载pdo_mysql扩展。'; +$lang->install->json = 'JSON扩展'; +$lang->install->jsonFail = '修改PHP配置文件,加载JSON扩展。'; +$lang->install->tmpRoot = '临时文件目录'; +$lang->install->dataRoot = '上传文件目录'; +$lang->install->mkdir = '

      需要创建目录%s。
      linux下面命令为:
      mkdir -p %s

      '; +$lang->install->chmod = '需要修改目录 "%s" 的权限。
      linux下面命令为:
      chmod o=rwx -R %s'; + +$lang->install->settingDB = '设置数据库'; +$lang->install->webRoot = 'PMS所在网站目录'; +$lang->install->requestType = 'URL方式'; +$lang->install->defaultLang = '默认语言'; +$lang->install->dbHost = '数据库服务器'; +$lang->install->dbHostNote = '如果localhost无法访问,尝试使用127.0.0.1'; +$lang->install->dbPort = '服务器端口'; +$lang->install->dbUser = '数据库用户名'; +$lang->install->dbPassword = '数据库密码'; +$lang->install->dbName = 'PMS使用的库'; +$lang->install->dbPrefix = '建表使用的前缀'; +$lang->install->createDB = '自动创建数据库'; +$lang->install->clearDB = '清空现有数据'; +$lang->install->importDemoData = '导入demo数据'; + +$lang->install->requestTypes['GET'] = '普通方式'; +$lang->install->requestTypes['PATH_INFO'] = '静态友好方式'; + +$lang->install->errorConnectDB = '数据库连接失败 '; +$lang->install->errorCreateDB = '数据库创建失败'; +$lang->install->errorTableExists = '数据表已经存在,您之前应该有安装过禅道,继续安装请返回前页并选择清空数据'; +$lang->install->errorCreateTable = '创建表失败'; +$lang->install->errorImportDemoData = '导入demo数据失败'; + +$lang->install->setConfig = '生成配置文件'; +$lang->install->key = '配置项'; +$lang->install->value = '值'; +$lang->install->saveConfig = '保存配置文件'; +$lang->install->save2File = '
      尝试写入配置文件,失败!
      拷贝上面文本框中的内容,将其保存到 " %s "中。您以后还可继续修改此配置文件。'; +$lang->install->saved2File = '配置信息已经成功保存到" %s "中。您后面还可继续修改此文件。'; +$lang->install->errorNotSaveConfig = '还没有保存配置文件'; + +$lang->install->getPriv = '设置帐号'; +$lang->install->company = '公司名称'; +$lang->install->pms = 'PMS地址'; +$lang->install->pmsNote = '即通过什么地址可以访问到禅道项目管理,设置域名或者IP地址即可,不需要http'; +$lang->install->account = '管理员帐号'; +$lang->install->password = '管理员密码'; +$lang->install->errorEmptyPassword = '密码不能为空'; + +$lang->install->success = "安装成功"; + +$lang->install->joinZentao = <<请及时删除install.php。现在您可以直接,设置用户及分组! +友情提示:为了您及时获得禅道的最新动态,请在禅道社区(www.zentao.net)进行登记。 +EOT; diff --git a/trunk/module/install/lang/zh-tw.php b/trunk/module/install/lang/zh-tw.php new file mode 100644 index 0000000000..2fa1aaab77 --- /dev/null +++ b/trunk/module/install/lang/zh-tw.php @@ -0,0 +1,112 @@ + + * @package install + * @version $Id: zh-tw.php 3374 2012-07-27 01:23:08Z wwccss $ + * @link http://www.zentao.net + */ +$lang->install->common = '安裝'; +$lang->install->next = '下一步'; +$lang->install->pre = '返回'; +$lang->install->reload = '刷新'; +$lang->install->error = '錯誤 '; + +$lang->install->start = '開始安裝'; +$lang->install->keepInstalling = '繼續安裝當前版本'; +$lang->install->seeLatestRelease = '看看最新的版本'; +$lang->install->welcome = '歡迎使用禪道項目管理軟件!'; +$lang->install->desc = <<青島易軟天創網絡科技有限公司開發。 +官方網站:http://www.zentao.net +技術支持: http://www.zentao.net/ask/ +新浪微博:t.sina.com.cn/zentaopms +騰訊微博:t.qq.com/zentaopms + +您現在正在安裝的版本是 %s。 +EOT; + +$lang->install->newReleased= "提示:官網網站已有最新版本%s, 發佈日期于 %s。"; +$lang->install->choice = '您可以選擇:'; +$lang->install->checking = '系統檢查'; +$lang->install->ok = '檢查通過(√)'; +$lang->install->fail = '檢查失敗(×)'; +$lang->install->loaded = '已加載'; +$lang->install->unloaded = '未加載'; +$lang->install->exists = '目錄存在 '; +$lang->install->notExists = '目錄不存在 '; +$lang->install->writable = '目錄可寫 '; +$lang->install->notWritable= '目錄不可寫 '; +$lang->install->phpINI = 'PHP配置檔案'; +$lang->install->checkItem = '檢查項'; +$lang->install->current = '當前配置'; +$lang->install->result = '檢查結果'; +$lang->install->action = '如何修改'; + +$lang->install->phpVersion = 'PHP版本'; +$lang->install->phpFail = 'PHP版本必須大於5.2.0'; + +$lang->install->pdo = 'PDO擴展'; +$lang->install->pdoFail = '修改PHP配置檔案,加載PDO擴展。'; +$lang->install->pdoMySQL = 'PDO_MySQL擴展'; +$lang->install->pdoMySQLFail = '修改PHP配置檔案,加載pdo_mysql擴展。'; +$lang->install->json = 'JSON擴展'; +$lang->install->jsonFail = '修改PHP配置檔案,加載JSON擴展。'; +$lang->install->tmpRoot = '臨時檔案目錄'; +$lang->install->dataRoot = '上傳檔案目錄'; +$lang->install->mkdir = '

      需要創建目錄%s。
      linux下面命令為:
      mkdir -p %s

      '; +$lang->install->chmod = '需要修改目錄 "%s" 的權限。
      linux下面命令為:
      chmod o=rwx -R %s'; + +$lang->install->settingDB = '設置資料庫'; +$lang->install->webRoot = 'PMS所在網站目錄'; +$lang->install->requestType = 'URL方式'; +$lang->install->defaultLang = '預設語言'; +$lang->install->dbHost = '資料庫伺服器'; +$lang->install->dbHostNote = '如果localhost無法訪問,嘗試使用127.0.0.1'; +$lang->install->dbPort = '伺服器連接埠'; +$lang->install->dbUser = '資料庫用戶名'; +$lang->install->dbPassword = '資料庫密碼'; +$lang->install->dbName = 'PMS使用的庫'; +$lang->install->dbPrefix = '建表使用的首碼'; +$lang->install->createDB = '自動創建資料庫'; +$lang->install->clearDB = '清空現有數據'; +$lang->install->importDemoData = '導入demo數據'; + +$lang->install->requestTypes['GET'] = '普通方式'; +$lang->install->requestTypes['PATH_INFO'] = '靜態友好方式'; + +$lang->install->errorConnectDB = '資料庫連接失敗 '; +$lang->install->errorCreateDB = '資料庫創建失敗'; +$lang->install->errorTableExists = '數據表已經存在,您之前應該有安裝過禪道,繼續安裝請返回前頁並選擇清空數據'; +$lang->install->errorCreateTable = '創建表失敗'; +$lang->install->errorImportDemoData = '導入demo數據失敗'; + +$lang->install->setConfig = '生成配置檔案'; +$lang->install->key = '配置項'; +$lang->install->value = '值'; +$lang->install->saveConfig = '保存配置檔案'; +$lang->install->save2File = '
      嘗試寫入配置檔案,失敗!
      拷貝上面文本框中的內容,將其保存到 " %s "中。您以後還可繼續修改此配置檔案。'; +$lang->install->saved2File = '配置信息已經成功保存到" %s "中。您後面還可繼續修改此檔案。'; +$lang->install->errorNotSaveConfig = '還沒有保存配置檔案'; + +$lang->install->getPriv = '設置帳號'; +$lang->install->company = '公司名稱'; +$lang->install->pms = 'PMS地址'; +$lang->install->pmsNote = '即通過什麼地址可以訪問到禪道項目管理,設置域名或者IP地址即可,不需要http'; +$lang->install->account = '管理員帳號'; +$lang->install->password = '管理員密碼'; +$lang->install->errorEmptyPassword = '密碼不能為空'; + +$lang->install->success = "安裝成功"; + +$lang->install->joinZentao = <<請及時刪除install.php。現在您可以直接,設置用戶及分組! +友情提示:為了您及時獲得禪道的最新動態,請在禪道社區(www.zentao.net)進行登記。 +EOT; diff --git a/trunk/module/install/model.php b/trunk/module/install/model.php new file mode 100644 index 0000000000..377412593a --- /dev/null +++ b/trunk/module/install/model.php @@ -0,0 +1,418 @@ + + * @package install + * @version $Id$ + * @link http://www.zentao.net + */ +?> +app->loadClass('snoopy'); + if(@$snoopy->fetchText('http://www.zentao.net/misc-getlatestrelease.json')) + { + $result = json_decode($snoopy->results); + if(isset($result->release) and $this->config->version != $result->release->version) + { + return $result->release; + } + } + return false; + } + + /** + * Check php version. + * + * @access public + * @return string ok|fail + */ + public function checkPHP() + { + return $result = version_compare(PHP_VERSION, '5.2.0') >= 0 ? 'ok' : 'fail'; + } + + /** + * Check PDO. + * + * @access public + * @return string ok|fail + */ + public function checkPDO() + { + return $result = extension_loaded('pdo') ? 'ok' : 'fail'; + } + + /** + * Check PDO::MySQL + * + * @access public + * @return string ok|fail + */ + public function checkPDOMySQL() + { + return $result = extension_loaded('pdo_mysql') ? 'ok' : 'fail'; + } + + /** + * Check json extension. + * + * @access public + * @return string ok|fail + */ + public function checkJSON() + { + return $result = extension_loaded('json') ? 'ok' : 'fail'; + } + + /** + * Get tempRoot info. + * + * @access public + * @return array + */ + public function getTmpRoot() + { + $result['path'] = $this->app->getTmpRoot(); + $result['exists'] = is_dir($result['path']); + $result['writable']= is_writable($result['path']); + return $result; + } + + /** + * Check tmpRoot. + * + * @access public + * @return string ok|fail + */ + public function checkTmpRoot() + { + $tmpRoot = $this->app->getTmpRoot(); + return $result = (is_dir($tmpRoot) and is_writable($tmpRoot)) ? 'ok' : 'fail'; + } + + /** + * Get data root + * + * @access public + * @return array + */ + public function getDataRoot() + { + $result['path'] = $this->app->getAppRoot() . 'www' . $this->app->getPathFix() . 'data'; + $result['exists'] = is_dir($result['path']); + $result['writable']= is_writable($result['path']); + return $result; + } + + /** + * Check the data root. + * + * @access public + * @return string ok|fail + */ + public function checkDataRoot() + { + $dataRoot = $this->app->getAppRoot() . 'www' . $this->app->getPathFix() . 'data'; + return $result = (is_dir($dataRoot) and is_writable($dataRoot)) ? 'ok' : 'fail'; + } + + /** + * Get the php.ini info. + * + * @access public + * @return string + */ + public function getIniInfo() + { + $iniInfo = ''; + ob_start(); + phpinfo(1); + $lines = explode("\n", strip_tags(ob_get_contents())); + ob_end_clean(); + foreach($lines as $line) if(strpos($line, 'ini') !== false) $iniInfo .= $line . "\n"; + return $iniInfo; + } + + /** + * Get web root. + * + * @access public + * @return string + */ + public function getWebRoot() + { + return rtrim(str_replace('\\', '/', pathinfo($_SERVER['PHP_SELF'], PATHINFO_DIRNAME)), '/') . '/'; + } + + /** + * Check config ok or not. + * + * @access public + * @return array + */ + public function checkConfig() + { + $return->result = 'ok'; + + /* Connect to database. */ + $this->setDBParam(); + $this->dbh = $this->connectDB(); + if(!is_object($this->dbh)) + { + $return->result = 'fail'; + $return->error = $this->lang->install->errorConnectDB . $this->dbh; + return $return; + } + + /* Get mysql version. */ + $version = $this->getMysqlVersion(); + + /* If database no exits, try create it. */ + if(!$this->dbExists()) + { + if(!$this->createDB($version)) + { + $return->result = 'fail'; + $return->error = $this->lang->install->errorCreateDB; + return $return; + } + } + elseif($this->tableExits() and $this->post->clearDB == false) + { + $return->result = 'fail'; + $return->error = $this->lang->install->errorTableExists; + return $return; + } + + /* Create tables. */ + if(!$this->createTable($version)) + { + $return->result = 'fail'; + $return->error = $this->lang->install->errorCreateTable; + return $return; + } + + return $return; + } + + /** + * Set database params. + * + * @access public + * @return void + */ + public function setDBParam() + { + $this->config->db->host = $this->post->dbHost; + $this->config->db->name = $this->post->dbName; + $this->config->db->user = $this->post->dbUser; + $this->config->db->password = $this->post->dbPassword; + $this->config->db->port = $this->post->dbPort; + $this->config->db->prefix = $this->post->dbPrefix; + + } + + /** + * Connect to database. + * + * @access public + * @return object + */ + public function connectDB() + { + $dsn = "mysql:host={$this->config->db->host}; port={$this->config->db->port}"; + try + { + $dbh = new PDO($dsn, $this->config->db->user, $this->config->db->password); + $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $dbh->exec("SET NAMES {$this->config->db->encoding}"); + return $dbh; + } + catch (PDOException $exception) + { + return $exception->getMessage(); + } + } + + /** + * Check db exits or not. + * + * @access public + * @return bool + */ + public function dbExists() + { + $sql = "SHOW DATABASES like '{$this->config->db->name}'"; + return $this->dbh->query($sql)->fetch(); + } + + /** + * Check table exits or not. + * + * @access public + * @return void + */ + public function tableExits() + { + $configTable = str_replace('`', "'", TABLE_CONFIG); + $sql = "SHOW TABLES FROM {$this->config->db->name} like $configTable"; + return $this->dbh->query($sql)->fetch(); + } + + /** + * Get mysql version. + * + * @access public + * @return string + */ + public function getMysqlVersion() + { + $sql = "SELECT VERSION() AS version"; + $result = $this->dbh->query($sql)->fetch(); + return substr($result->version, 0, 3); + } + + /** + * Create database. + * + * @param string $version + * @access public + * @return bool + */ + public function createDB($version) + { + $sql = "CREATE DATABASE `{$this->config->db->name}`"; + if($version > 4.1) $sql .= " DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci"; + return $this->dbh->query($sql); + } + + /** + * Create tables. + * + * @param string $version + * @access public + * @return bool + */ + public function createTable($version) + { + $dbFile = $this->app->getAppRoot() . 'db' . $this->app->getPathFix() . 'zentao.sql'; + $tables = explode(';', file_get_contents($dbFile)); + foreach($tables as $table) + { + $table = trim($table); + if(empty($table)) continue; + + if(strpos($table, 'CREATE') !== false and $version <= 4.1) + { + $table = str_replace('DEFAULT CHARSET=utf8', '', $table); + } + elseif(strpos($table, 'DROP') !== false and $this->post->clearDB != false) + { + $table = str_replace('--', '', $table); + } + $table = str_replace('`zt_', $this->config->db->name . '.`zt_', $table); + $table = str_replace('zt_', $this->config->db->prefix, $table); + if(!$this->dbh->query($table)) return false; + } + return true; + } + + /** + * Create a comapny, set admin. + * + * @access public + * @return void + */ + public function grantPriv() + { + if($this->post->password == '') die(js::error($this->lang->install->errorEmptyPassword)); + + /* Insert a company. */ + $company->name = $this->post->company; + $company->pms = $this->post->pms; + $company->admins = ",{$this->post->account},"; + $this->dao->insert(TABLE_COMPANY)->data($company)->autoCheck()->batchCheck('name, pms', 'notempty')->check('pms', 'unique')->exec(); + + if(!dao::isError()) + { + /* Set admin. */ + $companyID = $this->dbh->lastInsertID(); + $admin->account = $this->post->account; + $admin->realname = $this->post->account; + $admin->password = md5($this->post->password); + $admin->company = $companyID; + $this->dao->insert(TABLE_USER)->data($admin)->autoCheck()->check('account', 'notempty')->exec(); + + /* Update the group and groupPriv table. */ + $this->dao->update(TABLE_GROUP)->set('company')->eq($companyID)->exec($autoCompany = false); + $this->dao->update(TABLE_GROUPPRIV)->set('company')->eq($companyID)->exec($autoCompany = false); + } + } + + /** + * Import demo data. + * + * @access public + * @return void + */ + public function importDemoData() + { + $demoDataFile = $this->app->getAppRoot() . 'db' . $this->app->getPathFix() . 'demo.sql'; + $insertTables = explode(";\n", file_get_contents($demoDataFile)); + foreach($insertTables as $table) + { + $table = trim($table); + if(empty($table)) continue; + + $table = str_replace('`zt_', $this->config->db->name . '.`zt_', $table); + $table = str_replace('zt_', $this->config->db->prefix, $table); + if(!$this->dbh->query($table)) return false; + } + + $config->company = '1'; + $config->module = 'common'; + $config->owner = 'system'; + $config->section = 'global'; + $config->key = 'showDemoUsers'; + $config->value = '1'; + $this->dao->replace(TABLE_CONFIG)->data($config, false)->exec(false); + return true; + } + +} diff --git a/trunk/module/install/view/index.html.php b/trunk/module/install/view/index.html.php new file mode 100644 index 0000000000..301e88758a --- /dev/null +++ b/trunk/module/install/view/index.html.php @@ -0,0 +1,36 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + + + + + + +
      install->welcome;?>
      install->desc, $config->version));?>
      + version, $config->version) > 0)):?> + install->newReleased, $latestRelease);?> +

      + install->choice; + echo html::a($latestRelease->url, $lang->install->seeLatestRelease, '_blank'); + echo html::a($this->createLink('install', 'step1'), $lang->install->keepInstalling); + ?> +

      + +

      + config->langs, $app->cookie->lang, 'onchange="selectLang(this.value)"');?> + createLink('install', 'step1'), $lang->install->start);?> +

      + +
      + diff --git a/trunk/module/install/view/step1.html.php b/trunk/module/install/view/step1.html.php new file mode 100644 index 0000000000..df81b189a2 --- /dev/null +++ b/trunk/module/install/view/step1.html.php @@ -0,0 +1,96 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      install->checking;?>
      install->checkItem;?>install->current?>install->result?>install->action?>
      install->phpVersion;?>install->$phpResult;?>install->phpFail;?>
      install->pdo;?>install->loaded) : printf($lang->install->unloaded);?>install->$pdoResult;?>install->pdoFail;?>
      install->pdoMySQL;?>install->loaded) : printf($lang->install->unloaded);?>install->$pdoMySQLResult;?>install->pdoMySQLFail;?>
      install->json;?>install->loaded) : printf($lang->install->unloaded);?>install->$jsonResult;?>install->jsonFail;?>
      install->tmpRoot;?> + install->exists) : print($lang->install->notExists); + $tmpRootInfo['writable'] ? print($lang->install->writable) : print($lang->install->notWritable); + ?> + install->$tmpRootResult;?> + install->mkdir, $tmpRootInfo['path'], $tmpRootInfo['path']); + if(!$tmpRootInfo['writable']) printf($lang->install->chmod, $tmpRootInfo['path'], $tmpRootInfo['path']); + ?> +
      install->dataRoot;?> + install->exists) : print($lang->install->notExists); + $dataRootInfo['writable'] ? print($lang->install->writable) : print($lang->install->notWritable); + ?> + install->$dataRootResult;?> + install->mkdir, $dataRootInfo['path'], $dataRootInfo['path']); + if(!$dataRootInfo['writable']) printf($lang->install->chmod, $dataRootInfo['path'], $dataRootInfo['path']); + ?> +
      + createLink('install', 'step2'), $lang->install->next); + } + else + { + echo html::a($this->createLink('install', 'step1'), $lang->install->reload); + if($pdoResult == 'fail' or $pdoMySQLResult == 'fail') + { + echo '

      ' . '' . $lang->install->phpINI . '
      ' . nl2br($this->install->getIniInfo()) . '

      '; + } + } + ?> +
      + diff --git a/trunk/module/install/view/step2.html.php b/trunk/module/install/view/step2.html.php new file mode 100644 index 0000000000..d782b23903 --- /dev/null +++ b/trunk/module/install/view/step2.html.php @@ -0,0 +1,67 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + +
      '> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      install->setConfig;?>
      install->key;?>install->value?>
      install->webRoot;?>
      install->requestType;?>install->requestTypes, 'GET', 'class=select-3');?>
      install->defaultLang;?>langs, $app->getClientLang(), 'class=select-3');?>
      install->dbHost;?>install->dbHostNote;?>
      install->dbPort;?>
      install->dbUser;?>
      install->dbPassword;?>
      install->dbName;?>
      install->dbPrefix;?> + install->clearDB); + ?> +
      +
      + diff --git a/trunk/module/install/view/step3.html.php b/trunk/module/install/view/step3.html.php new file mode 100644 index 0000000000..dfe89b1fc7 --- /dev/null +++ b/trunk/module/install/view/step3.html.php @@ -0,0 +1,72 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + +installed = true; +\$config->debug = false; +\$config->requestType = '$requestType'; +\$config->db->host = '$dbHost'; +\$config->db->port = '$dbPort'; +\$config->db->name = '$dbName'; +\$config->db->user = '$dbUser'; +\$config->db->password = '$dbPassword'; +\$config->db->prefix = '$dbPrefix'; +\$config->webRoot = '$webRoot'; +\$config->default->domain = '$domain'; +\$config->default->lang = '$defaultLang'; +EOT; +} +?> + + + + + +
      install->error;?>
      install->pre, "onclick='javascript:history.back(-1)'");?>
      + + + + + + + + + +
      install->saveConfig;?>
      + app->getConfigRoot(); + $myConfigFile = $configRoot . 'my.php'; + if(is_writable($configRoot)) + { + if(@file_put_contents($myConfigFile, $configContent)) + { + printf($lang->install->saved2File, $myConfigFile); + } + else + { + printf($lang->install->save2File, $this->app->getConfigRoot() . 'my.php'); + } + } + else + { + printf($lang->install->save2File, $this->app->getConfigRoot() . 'my.php'); + } + echo "
      "; + echo "
      " . html::a($this->createLink('install', 'step4'), $lang->install->next) . '
      '; + ?> +
      + + diff --git a/trunk/module/install/view/step4.html.php b/trunk/module/install/view/step4.html.php new file mode 100644 index 0000000000..527ae694f7 --- /dev/null +++ b/trunk/module/install/view/step4.html.php @@ -0,0 +1,52 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + + + + + + +
      install->error;?>
      install->pre, "onclick='javascript:history.back(-1)'");?>
      + + + + + +
      install->success;?>
      install->afterSuccess;?>
      install->pre, "onclick='javascript:history.back(-1)'");?>
      + +
      + + + + + + + + + + + + + + + + + + + + + +
      install->getPriv;?>
      install->company;?>
      install->pms;?>{$lang->install->pmsNote}";?>
      install->account;?>
      install->password;?>install->importDemoData);?>
      +
      + + diff --git a/trunk/module/install/view/step5.html.php b/trunk/module/install/view/step5.html.php new file mode 100644 index 0000000000..a0edd989f7 --- /dev/null +++ b/trunk/module/install/view/step5.html.php @@ -0,0 +1,21 @@ + + * @package ZenTaoPMS + * @version $Id: step5.html.php 2568 2012-02-18 15:53:35Z zhujinyong@cnezsoft.com$ + */ +?> + + + + + + +
      install->success;?>
      + install->joinZentao, $config->version, $this->createLink('admin', 'register'), $this->createLink('admin', 'bind'), inlink('step6')));?> +
      + diff --git a/trunk/module/mail/config.php b/trunk/module/mail/config.php new file mode 100644 index 0000000000..46df89376c --- /dev/null +++ b/trunk/module/mail/config.php @@ -0,0 +1,44 @@ +mail->turnon = 0; // trun on email feature or not. +$config->mail->fromAddress = ''; // The from address. +$config->mail->fromName = 'zentao'; // The from name. +$config->mail->mta = 'smtp'; // smtp|phpmail. + +/* SMTP settings. */ +if($config->mail->mta == 'smtp') +{ + $config->mail->smtp->debug = 0; // Debug level, 0,1,2. + $config->mail->smtp->auth = true; // Need auth or not. + $config->mail->smtp->host = 'localhost'; // The smtp server host address. + $config->mail->smtp->port = '25'; // The smtp server host port. + $config->mail->smtp->secure = ''; // The type to encode datas, 'ssl' or 'tls' allowed + $config->mail->smtp->username = ''; // The smtp user, may be a full email adress. + $config->mail->smtp->password = ''; // The smtp user's password. +} + +$config->mail->provider['163.com']->host = 'smtp.163.com'; +$config->mail->provider['yeah.net']->host = 'smtp.yeah.net'; +$config->mail->provider['netease.com']->host = 'smtp.netease.com'; +$config->mail->provider['126.com']->host = 'smtp.126.com'; +$config->mail->provider['qiye.163.com']->host = 'smtp.qiye.163.com'; + +$config->mail->provider['sina.com']->host = 'smtp.sina.com'; +$config->mail->provider['sina.cn']->host = 'smtp.sina.cn'; +$config->mail->provider['vip.sina.com']->host = 'smtp.vip.sina.com'; +$config->mail->provider['sina.net']->host = 'smtp.sina.net'; + +$config->mail->provider['sohu.com']->host = 'smtp.sohu.com'; +$config->mail->provider['vip.sohu.com']->host = 'smtp.vip.sohu.com'; + +$config->mail->provider['21cn.com']->host = 'smtp.21cn.com'; + +$config->mail->provider['qq.com']->host = 'smtp.qq.com'; + +$config->mail->provider['gmail.com']->host = 'smtp.gmail.com'; +$config->mail->provider['gmail.com']->secure = 'ssl'; +$config->mail->provider['gmail.com']->port = '465'; +$config->mail->provider['google.com'] = $config->mail->provider['gmail.com']; +$config->mail->provider['googlemail.com'] = $config->mail->provider['gmail.com']; + +$config->mail->provider['263.net']->host = 'smtp.263.net'; +$config->mail->provider['263xmail.com']->host = 'smtp.263xmail.com'; diff --git a/trunk/module/mail/control.php b/trunk/module/mail/control.php new file mode 100755 index 0000000000..c07f75809c --- /dev/null +++ b/trunk/module/mail/control.php @@ -0,0 +1,152 @@ + + * @package mail + * @version $Id$ + * @link http://www.zentao.net + */ +class mail extends control +{ + public function index() + { + if($this->config->mail->turnon) $this->locate(inlink('edit')); + $this->locate(inlink('detect')); + } + + /** + * Detect email config auto. + * + * @access public + * @return void + */ + public function detect() + { + if($_POST) + { + $error = ''; + if($this->post->fromAddress == false) $error = sprintf($this->lang->error->notempty, $this->lang->mail->fromAddress); + if(!validater::checkEmail($this->post->fromAddress)) $error .= '\n' . sprintf($this->lang->error->email, $this->lang->mail->fromAddress); + + if($error) die(js::alert($error)); + + $mailConfig = $this->mail->autoDetect($this->post->fromAddress); + $mailConfig->fromAddress = $this->post->fromAddress; + $this->session->set('mailConfig', $mailConfig); + + die(js::locate(inlink('edit'), 'parent')); + } + + $this->view->header->title = $this->lang->mail->detect; + $this->view->position[] = html::a(inlink('index'), $this->lang->mail->common); + $this->view->position[] = $this->lang->mail->detect; + + $this->view->fromAddress = $this->session->mailConfig ? $this->session->mailConfig->fromAddress : ''; + + $this->display(); + } + + /** + * Edit the mail config. + * + * @access public + * @return void + */ + public function edit() + { + if($this->config->mail->turnon) + { + $mailConfig = $this->config->mail->smtp; + $mailConfig->fromAddress = $this->config->mail->fromAddress; + $mailConfig->fromName = $this->config->mail->fromName; + } + elseif($this->session->mailConfig) + { + $mailConfig = $this->session->mailConfig; + } + else + { + $this->locate(inlink('detect')); + } + + $this->view->header->title = $this->lang->mail->edit; + $this->view->position[] = html::a(inlink('index'), $this->lang->mail->common); + $this->view->position[] = $this->lang->mail->edit; + + $this->view->mailConfig = $mailConfig; + $this->display(); + } + + /** + * Save the email config. + * + * @access public + * @return void + */ + public function save() + { + if(!empty($_POST)) + { + $mailConfig = <<mail->turnon = {$this->post->turnon}; +\$config->mail->mta = 'smtp'; +\$config->mail->fromAddress = "{$this->post->fromAddress}"; +\$config->mail->fromName = "{$this->post->fromName}"; +\$config->mail->smtp->host = "{$this->post->host}"; +\$config->mail->smtp->port = "{$this->post->port}"; +\$config->mail->smtp->auth = {$this->post->auth}; +\$config->mail->smtp->username = "{$this->post->username}"; +\$config->mail->smtp->password = "{$this->post->password}"; +\$config->mail->smtp->secure = "{$this->post->secure}"; +\$config->mail->smtp->debug = {$this->post->debug}; +EOT; + + /* Output config to the extconfig file of mail */ + $configPath = $this->app->getModuleExtPath('mail', 'config'); + $configFile = $configPath . 'zzzemail.php'; + $saved = false; + if(is_file($configFile) and is_writable($configFile)) $saved = file_put_contents($configFile, $mailConfig); + if(!is_file($configFile) and is_writable($configPath)) $saved = file_put_contents($configFile, $mailConfig); + + if($saved) $this->session->set('mailConfig', ''); + + $this->view->header->title = $this->lang->mail->save; + $this->view->position[] = html::a(inlink('index'), $this->lang->mail->common); + $this->view->position[] = $this->lang->mail->save; + + $this->view->mailConfig = $mailConfig; + $this->view->configPath = $configPath; + $this->view->configFile = $configFile; + $this->view->saved = $saved; + $this->display(); + } + } + + /** + * Send test email. + * + * @access public + * @return void + */ + public function test() + { + if(!$this->config->mail->turnon) + { + die(js::alert($this->lang->mail->needConfigure) . js::locate('back')); + } + + if($_POST) + { + $this->mail->send($this->post->to, $this->lang->mail->subject, $this->lang->mail->content,"", true); + if($this->mail->isError()) die(js::error($this->mail->getError())); + die(js::alert($this->lang->mail->successSended)); + } + + $this->view->users = $this->dao->select('account, CONCAT(realname, " ", email) AS email' )->from(TABLE_USER)->where('email')->ne('')->orderBy('account')->fetchPairs(); + $this->display(); + } +} diff --git a/trunk/module/mail/js/detect.js b/trunk/module/mail/js/detect.js new file mode 100644 index 0000000000..06cfea9964 --- /dev/null +++ b/trunk/module/mail/js/detect.js @@ -0,0 +1,4 @@ +$(document).ready() +{ + $('#fromAddress').focus(); +} diff --git a/trunk/module/mail/js/edit.js b/trunk/module/mail/js/edit.js new file mode 100644 index 0000000000..ad537f83eb --- /dev/null +++ b/trunk/module/mail/js/edit.js @@ -0,0 +1,4 @@ +$(document).ready() +{ + $('#password').focus(); +} diff --git a/trunk/module/mail/lang/en.php b/trunk/module/mail/lang/en.php new file mode 100755 index 0000000000..79fb25a97c --- /dev/null +++ b/trunk/module/mail/lang/en.php @@ -0,0 +1,42 @@ +mail->common = 'Email setting'; +$lang->mail->index = 'Index'; +$lang->mail->detect = 'Detect'; +$lang->mail->edit = 'Configure'; +$lang->mail->save = 'Save Configuration'; +$lang->mail->test = 'Testing'; + +$lang->mail->turnon = 'Turnon'; +$lang->mail->fromAddress = 'From email'; +$lang->mail->fromName = 'From title'; +$lang->mail->mta = 'MTA'; +$lang->mail->host = 'SMTP host'; +$lang->mail->port = 'SMTP port'; +$lang->mail->auth = 'Authentication'; +$lang->mail->username = 'SMTP account'; +$lang->mail->password = 'SMTP password'; +$lang->mail->secure = 'Secure'; +$lang->mail->debug = 'Debug'; + +$lang->mail->turnonList[1] = 'on'; +$lang->mail->turnonList[0] = 'off'; + +$lang->mail->debugList[0] = 'off'; +$lang->mail->debugList[1] = 'normal'; +$lang->mail->debugList[2] = 'high'; + +$lang->mail->authList[1] = 'necessary'; +$lang->mail->authList[0] = 'unnecessary'; + +$lang->mail->secureList[''] = 'plain'; +$lang->mail->secureList['ssl'] = 'ssl'; +$lang->mail->secureList['tls'] = 'tls'; + +$lang->mail->inputFromEmail = 'Please input the from email:'; +$lang->mail->nextStep = 'Next'; +$lang->mail->successSaved = 'The configuration successfully saved to: %s'; +$lang->mail->saveManual = 'Please save the configuration to: %s'; +$lang->mail->subject = "It's a testing email from zentao."; +$lang->mail->content = 'If you can see this, the email notification feature can work now!'; +$lang->mail->successSended = 'Successfully sended!'; +$lang->mail->needConfigure = "I can not find the configuration, please configure it first."; diff --git a/trunk/module/mail/lang/zh-cn.php b/trunk/module/mail/lang/zh-cn.php new file mode 100755 index 0000000000..ed9bb4ea43 --- /dev/null +++ b/trunk/module/mail/lang/zh-cn.php @@ -0,0 +1,42 @@ +mail->common = '发信配置'; +$lang->mail->index = '首页'; +$lang->mail->detect = '检测'; +$lang->mail->edit = '编辑配置'; +$lang->mail->save = '保存配置'; +$lang->mail->test = '测试发信'; + +$lang->mail->turnon = '是否打开'; +$lang->mail->fromAddress = '发信邮箱'; +$lang->mail->fromName = '发信人'; +$lang->mail->mta = '发信方式'; +$lang->mail->host = 'smtp服务器'; +$lang->mail->port = 'smtp端口号'; +$lang->mail->auth = '是否需要验证'; +$lang->mail->username = 'smtp帐号'; +$lang->mail->password = 'smtp密码'; +$lang->mail->secure = '是否加密'; +$lang->mail->debug = '调试级别'; + +$lang->mail->turnonList[1] = '打开'; +$lang->mail->turnonList[0] = '关闭'; + +$lang->mail->debugList[0] = '关闭'; +$lang->mail->debugList[1] = '一般'; +$lang->mail->debugList[2] = '较高'; + +$lang->mail->authList[1] = '需要'; +$lang->mail->authList[0] = '不需要'; + +$lang->mail->secureList[''] = '不加密'; +$lang->mail->secureList['ssl'] = 'ssl'; +$lang->mail->secureList['tls'] = 'tls'; + +$lang->mail->inputFromEmail = '请输入发信邮箱:'; +$lang->mail->nextStep = '下一步'; +$lang->mail->successSaved = '配置信息已经成功保存到:%s'; +$lang->mail->saveManual = '请将该配置信息保存到:%s'; +$lang->mail->subject = '测试邮件'; +$lang->mail->content = '邮箱设置成功'; +$lang->mail->successSended = '成功发送!'; +$lang->mail->needConfigure = '无法找到邮件配置信息,请先配置邮件发送参数。'; diff --git a/trunk/module/mail/lang/zh-tw.php b/trunk/module/mail/lang/zh-tw.php new file mode 100644 index 0000000000..63b98ddb21 --- /dev/null +++ b/trunk/module/mail/lang/zh-tw.php @@ -0,0 +1,42 @@ +mail->common = '發信配置'; +$lang->mail->index = '首頁'; +$lang->mail->detect = '檢測'; +$lang->mail->edit = '編輯配置'; +$lang->mail->save = '保存配置'; +$lang->mail->test = '測試發信'; + +$lang->mail->turnon = '是否打開'; +$lang->mail->fromAddress = '發信郵箱'; +$lang->mail->fromName = '發信人'; +$lang->mail->mta = '發信方式'; +$lang->mail->host = 'smtp伺服器'; +$lang->mail->port = 'smtp連接埠號'; +$lang->mail->auth = '是否需要驗證'; +$lang->mail->username = 'smtp帳號'; +$lang->mail->password = 'smtp密碼'; +$lang->mail->secure = '是否加密'; +$lang->mail->debug = '調試級別'; + +$lang->mail->turnonList[1] = '打開'; +$lang->mail->turnonList[0] = '關閉'; + +$lang->mail->debugList[0] = '關閉'; +$lang->mail->debugList[1] = '一般'; +$lang->mail->debugList[2] = '較高'; + +$lang->mail->authList[1] = '需要'; +$lang->mail->authList[0] = '不需要'; + +$lang->mail->secureList[''] = '不加密'; +$lang->mail->secureList['ssl'] = 'ssl'; +$lang->mail->secureList['tls'] = 'tls'; + +$lang->mail->inputFromEmail = '請輸入發信郵箱:'; +$lang->mail->nextStep = '下一步'; +$lang->mail->successSaved = '配置信息已經成功保存到:%s'; +$lang->mail->saveManual = '請將該配置信息保存到:%s'; +$lang->mail->subject = '測試郵件'; +$lang->mail->content = '郵箱設置成功'; +$lang->mail->successSended = '成功發送!'; +$lang->mail->needConfigure = '無法找到郵件配置信息,請先配置郵件發送參數。'; diff --git a/trunk/module/mail/model.php b/trunk/module/mail/model.php new file mode 100644 index 0000000000..7e7d94c83a --- /dev/null +++ b/trunk/module/mail/model.php @@ -0,0 +1,395 @@ + + * @package mail + * @version $Id$ + * @link http://www.zentao.net + */ +?> +app->loadClass('phpmailer', $static = true); + $this->setMTA(); + } + + /** + * Auto detect email config. + * + * @param string $email + * @access public + * @return object + */ + public function autoDetect($email) + { + /* Split the email to username and domain. */ + list($username, $domain) = explode('@', $email); + $domain = strtolower($domain); + + /* + * 1. try to find config from the providers. + * 2. try to find the mx record to get the domain and then search it in providers. + * 3. try smtp.$domain's 25 and 465 port, if can connect, use smtp.$domain. + */ + $config = $this->getConfigFromProvider($domain, $username); + if(!$config) $config = $this->getConfigByMXRR($domain, $username); + if(!$config) $config = $this->getConfigByDetectingSMTP($domain, $username, 25); + if(!$config) $config = $this->getConfigByDetectingSMTP($domain, $username, 465); + + /* Set default values. */ + $config->mta = 'smtp'; + $config->fromName = 'zentao'; + $config->password = ''; + $config->debug = 1; + if(!isset($config->host)) $config->host = ''; + if(!isset($config->auth)) $config->auth = 1; + if(!isset($config->port)) $config->port = '25'; + + return $config; + } + + /** + * Try get config from providers. + * + * @param int $domain + * @param int $username + * @access public + * @return bool|object + */ + public function getConfigFromProvider($domain, $username) + { + if(isset($this->config->mail->provider[$domain])) + { + $config = $this->config->mail->provider[$domain]; + $config->mta = 'smtp'; + $config->username = $username; + $config->auth = 1; + if(!isset($config->port)) $config->port = 25; + if(!isset($config->secure)) $config->secure = ''; + return $config; + } + return false; + } + + /** + * Get config by MXRR. + * + * @param string $domain + * @param string $username + * @access public + * @return bool|object + */ + public function getConfigByMXRR($domain, $username) + { + /* Try to get mx record, under linux, use getmxrr() directly, windows use nslookup. */ + if(function_exists('getmxrr')) + { + getmxrr($domain, $smtpHosts); + } + elseif(strpos(PHP_OS, 'WIN') !== false) + { + $smtpHosts = array(); + $result = `nslookup -q=mx {$domain} 2>nul`; + $lines = explode("\n", $result); + foreach($lines as $line) + { + if(stripos($line, 'exchanger')) $smtpHosts[] = trim(substr($line, strrpos($line, '=') + 1)); + } + } + + /* Cycle the smtpHosts and try to find it's config from the provider config. */ + foreach($smtpHosts as $smtpHost) + { + /* Get the domain name from the hosts, for example: imxbiz1.qq.com get qq.com. */ + $smtpDomain = explode('.', $smtpHost); + array_shift($smtpDomain); + $smtpDomain = strtolower(implode('.', $smtpDomain)); + if($config = $this->getConfigFromProvider($smtpDomain, $username)) + { + $config->username = "$username@$domain"; + return $config; + } + } + + return false; + } + + /** + * Try connect to smtp.$domain's 25 or 465 port and compute the config according to the connection result. + * + * @param string $domain + * @param string $username + * @param int $port + * @access public + * @return bool|object + */ + public function getConfigByDetectingSMTP($domain, $username, $port) + { + $host = 'smtp.' . $domain; + ini_set('default_socket_timeout', 3); + $connection = @fsockopen($host, $port); + if(!$connection) return false; + fclose($connection); + + $config->username = $username; + $config->host = $host; + $config->auth = 1; + $config->port = $port; + $config->secure = $port == 465 ? 'ssl' : ''; + + return $config; + } + + /** + * Set MTA. + * + * @access public + * @return void + */ + public function setMTA() + { + if(self::$instance == null) self::$instance = new phpmailer(true); + $this->mta = self::$instance; + $this->mta->CharSet = $this->config->encoding; + $funcName = "set{$this->config->mail->mta}"; + if(!method_exists($this, $funcName)) echo $this->app->error("The MTA {$this->config->mail->mta} not supported now.", __FILE__, __LINE__, $exit = false); + $this->$funcName(); + } + + /** + * Set smtp. + * + * @access public + * @return void + */ + public function setSMTP() + { + $this->mta->isSMTP(); + $this->mta->SMTPDebug = $this->config->mail->smtp->debug; + $this->mta->Host = $this->config->mail->smtp->host; + $this->mta->SMTPAuth = $this->config->mail->smtp->auth; + $this->mta->Username = $this->config->mail->smtp->username; + $this->mta->Password = $this->config->mail->smtp->password; + if(isset($this->config->mail->smtp->port)) $this->mta->Port = $this->config->mail->smtp->port; + if(isset($this->config->mail->smtp->secure) and !empty($this->config->mail->smtp->secure))$this->mta->SMTPSecure = strtolower($this->config->mail->smtp->secure); + } + + /** + * PHPmail. + * + * @access public + * @return void + */ + public function setPhpMail() + { + $this->mta->isMail(); + } + + /** + * Sendmail. + * + * @access public + * @return void + */ + public function setSendMail() + { + $this->mta->isSendmail(); + } + + /** + * Gmail. + * + * @access public + * @return void + */ + public function setGMail() + { + $this->mta->isSMTP(); + $this->mta->SMTPDebug = $this->config->mail->gmail->debug; + $this->mta->Host = 'smtp.gmail.com'; + $this->mta->Port = 465; + $this->mta->SMTPSecure = "ssl"; + $this->mta->SMTPAuth = true; + $this->mta->Username = $this->config->mail->gmail->username; + $this->mta->Password = $this->config->mail->gmail->password; + } + + /** + * Send email + * + * @param array $toList + * @param string $subject + * @param string $body + * @param array $ccList + * @param bool $includeMe + * @access public + * @return void + */ + public function send($toList, $subject, $body = '', $ccList = '', $includeMe = false) + { + if(!$this->config->mail->turnon) return; + + /* Process toList and ccList, remove current user from them. If toList is empty, use the first cc as to. */ + if($includeMe == false) + { + $account = isset($this->app->user->account) ? $this->app->user->account : ''; + $toList = $toList ? explode(',', str_replace(' ', '', $toList)) : array(); + $ccList = $ccList ? explode(',', str_replace(' ', '', $ccList)) : array(); + + foreach($toList as $key => $to) if(trim($to) == $account or !trim($to)) unset($toList[$key]); + foreach($ccList as $key => $cc) if(trim($cc) == $account or !trim($cc)) unset($ccList[$key]); + + if(!$toList and !$ccList) return; + if(!$toList and $ccList) $toList = array(array_shift($ccList)); + $toList = join(',', $toList); + $ccList = join(',', $ccList); + } + + /* Get realname and email of users. */ + $this->loadModel('user'); + $emails = $this->user->getRealNameAndEmails(str_replace(' ', '', $toList . ',' . $ccList)); + + $this->clear(); + + try + { + $this->mta->setFrom($this->config->mail->fromAddress, $this->config->mail->fromName); + $this->setSubject($subject); + $this->setTO($toList, $emails); + $this->setCC($ccList, $emails); + $this->setBody($body); + $this->setErrorLang(); + $this->mta->send(); + } + catch (phpmailerException $e) + { + $this->errors[] = trim(strip_tags($e->errorMessage())); + } + catch (Exception $e) + { + $this->errors[] = trim(strip_tags($e->getMessage())); + } + } + + /** + * Set to address + * + * @param array $toList + * @param array $emails + * @access public + * @return void + */ + public function setTO($toList, $emails) + { + $toList = explode(',', str_replace(' ', '', $toList)); + foreach($toList as $account) + { + if(!isset($emails[$account]) or isset($emails[$account]->sended) or strpos($emails[$account]->email, '@') == false) continue; + $this->mta->addAddress($emails[$account]->email, $emails[$account]->realname); + $emails[$account]->sended = true; + } + } + + /** + * Set cc. + * + * @param array $ccList + * @param array $emails + * @access public + * @return void + */ + public function setCC($ccList, $emails) + { + $ccList = explode(',', str_replace(' ', '', $ccList)); + if(!is_array($ccList)) return; + foreach($ccList as $account) + { + if(!isset($emails[$account]) or isset($emails[$account]->sended) or strpos($emails[$account]->email, '@') == false) continue; + $this->mta->addCC($emails[$account]->email, $emails[$account]->realname); + $emails[$account]->sended = true; + } + } + + /** + * Set subject + * + * @param string $subject + * @access public + * @return void + */ + public function setSubject($subject) + { + $this->mta->Subject = stripslashes($subject); + } + + /** + * Set body. + * + * @param string $body + * @access public + * @return void + */ + public function setBody($body) + { + $this->mta->msgHtml("$body"); + } + + /** + * Set error lang. + * + * @access public + * @return void + */ + public function setErrorLang() + { + $this->mta->SetLanguage($this->app->getClientLang()); + } + + /** + * Clear. + * + * @access public + * @return void + */ + public function clear() + { + $this->mta->clearAddresses(); + $this->mta->clearAttachments(); + } + + /** + * Is error? + * + * @access public + * @return bool + */ + public function isError() + { + return !empty($this->errors); + } + + /** + * Get errors. + * + * @access public + * @return void + */ + public function getError() + { + $errors = $this->errors; + $this->errors = array(); + return $errors; + } +} diff --git a/trunk/module/mail/view/detect.html.php b/trunk/module/mail/view/detect.html.php new file mode 100755 index 0000000000..0522a3d260 --- /dev/null +++ b/trunk/module/mail/view/detect.html.php @@ -0,0 +1,20 @@ + + * @package mail + * @version $Id$ + * @link http://www.zentao.net + */ +include '../../common/view/header.html.php'; +?> +
      + + + +
      mail->inputFromEmail; ?>
      mail->nextStep);?>
      +
      + diff --git a/trunk/module/mail/view/edit.html.php b/trunk/module/mail/view/edit.html.php new file mode 100755 index 0000000000..c1eda7e4cc --- /dev/null +++ b/trunk/module/mail/view/edit.html.php @@ -0,0 +1,68 @@ + + * @package mail + * @version $Id$ + * @link http://www.zentao.net + */ +include '../../common/view/header.html.php'; +?> +
      ' id='dataform'> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      mail->edit;?>
      mail->turnon; ?>mail->turnonList, 1);?>
      mail->fromAddress; ?>fromAddress, 'class=text-3');?>
      mail->fromName; ?>fromName, 'class=text-3');?>
      mail->host; ?>host, 'class=text-3');?>
      mail->port; ?>port, 'class=text-3');?>
      mail->auth; ?>mail->authList, $mailConfig->auth, 'onchange=setAuth(this.value)'); ?>
      mail->username; ?>username, 'class=text-3') ?>
      mail->password; ?>password, 'class="text-3" autocomplete="off"') ?>
      mail->secure; ?>mail->secureList, $mailConfig->secure); ?>
      mail->debug; ?>mail->debugList, $mailConfig->debug);?>
      + config->mail->turnon) echo html::linkButton($lang->mail->test, inlink('test')); + ?> +
      +
      + diff --git a/trunk/module/mail/view/save.html.php b/trunk/module/mail/view/save.html.php new file mode 100755 index 0000000000..cd1f883cb6 --- /dev/null +++ b/trunk/module/mail/view/save.html.php @@ -0,0 +1,27 @@ + + * @package mail + * @version $Id$ + * @link http://www.zentao.net + */ +include '../../common/view/header.html.php'; +?> + + + + + +
      mail->save ?>
      +
      '; + if($saved) printf($lang->mail->successSaved, $configFile); + if(!$saved) printf($lang->mail->saveManual, $configFile); + echo html::linkButton($lang->mail->test, inlink('test')); + ?> +
      + diff --git a/trunk/module/mail/view/test.html.php b/trunk/module/mail/view/test.html.php new file mode 100755 index 0000000000..699537e83c --- /dev/null +++ b/trunk/module/mail/view/test.html.php @@ -0,0 +1,28 @@ + + * @package mail + * @version $Id$ + * @link http://www.zentao.net + */ +include '../../common/view/header.html.php'; +?> +
      + + + + + +
      mail->test; ?>
      + user->account); + echo html::submitButton($lang->mail->test); + echo html::linkButton($lang->mail->edit, $this->inLink('edit')); + ?> +
      +
      + diff --git a/trunk/module/misc/control.php b/trunk/module/misc/control.php new file mode 100644 index 0000000000..cd08c04dd1 --- /dev/null +++ b/trunk/module/misc/control.php @@ -0,0 +1,86 @@ + + * @package misc + * @version $Id$ + * @link http://www.zentao.net + */ +class misc extends control +{ + /** + * Ping the server every 5 minutes to keep the session. + * + * @access public + * @return void + */ + public function ping() + { + if(mt_rand(0, 1) == 1) $this->loadModel('setting')->setSN(); + die(""); + } + + /** + * Show php info. + * + * @access public + * @return void + */ + public function phpinfo() + { + die(phpinfo()); + } + + /** + * Show about info of zentao. + * + * @access public + * @return void + */ + public function about() + { + die($this->display()); + } + + /** + * Update nl. + * + * @access public + * @return void + */ + public function updateNL() + { + $this->loadModel('upgrade')->updateNL(); + } + + /** + * Check current version is latest or not. + * + * @access public + * @return void + */ + public function checkUpdate() + { + $note = isset($_GET['note']) ? $_GET['note'] : ''; + $browser = isset($_GET['browser']) ? $_GET['browser'] : ''; + + $this->view->note = urldecode(helper::safe64Decode($note)); + $this->view->browser = $browser; + $this->display(); + } + + /** + * Check model extension logic + * + * @access public + * @return void + */ + public function checkExtension() + { + echo $this->misc->hello(); + echo $this->misc->hello2(); + } +} diff --git a/trunk/module/misc/css/about.css b/trunk/module/misc/css/about.css new file mode 100644 index 0000000000..87f55f7b3f --- /dev/null +++ b/trunk/module/misc/css/about.css @@ -0,0 +1,4 @@ +body{background:white; margin:20px 10px 0 0; padding-right:20px} +.colhead th {text-align:left; padding-left:16px} +li{margin:2px 0} +#proversion, #zentaotrain{color:red} diff --git a/trunk/module/misc/ext/config/config1.php b/trunk/module/misc/ext/config/config1.php new file mode 100644 index 0000000000..f96b90bc33 --- /dev/null +++ b/trunk/module/misc/ext/config/config1.php @@ -0,0 +1,2 @@ +misc->key1 = 'value1'; diff --git a/trunk/module/misc/ext/config/config2.php b/trunk/module/misc/ext/config/config2.php new file mode 100644 index 0000000000..d8e580d506 --- /dev/null +++ b/trunk/module/misc/ext/config/config2.php @@ -0,0 +1,2 @@ +misc->key2 = 'value2'; diff --git a/trunk/module/misc/ext/control/getsid.php b/trunk/module/misc/ext/control/getsid.php new file mode 100644 index 0000000000..6440048963 --- /dev/null +++ b/trunk/module/misc/ext/control/getsid.php @@ -0,0 +1,13 @@ +view->header->title = 'getsid'; + $this->view->sid = session_id(); + $this->view->test = $this->misc->test(); + $this->display(); + } +} +?> diff --git a/trunk/module/misc/ext/lang/zh-cn1.php b/trunk/module/misc/ext/lang/zh-cn1.php new file mode 100644 index 0000000000..bb96085b35 --- /dev/null +++ b/trunk/module/misc/ext/lang/zh-cn1.php @@ -0,0 +1,2 @@ +misc->item1 = 'item1'; diff --git a/trunk/module/misc/ext/lang/zh-cn2.php b/trunk/module/misc/ext/lang/zh-cn2.php new file mode 100644 index 0000000000..61c2dfc2d7 --- /dev/null +++ b/trunk/module/misc/ext/lang/zh-cn2.php @@ -0,0 +1,2 @@ +misc->item2 = 'item2'; diff --git a/trunk/module/misc/ext/model/class/test.class.php b/trunk/module/misc/ext/model/class/test.class.php new file mode 100644 index 0000000000..ecbf5aed42 --- /dev/null +++ b/trunk/module/misc/ext/model/class/test.class.php @@ -0,0 +1,8 @@ +"; + } +} diff --git a/trunk/module/misc/ext/model/ext.php b/trunk/module/misc/ext/model/ext.php new file mode 100644 index 0000000000..aa2f873704 --- /dev/null +++ b/trunk/module/misc/ext/model/ext.php @@ -0,0 +1,6 @@ +loadExtension('test')->hello(); // Load testMisc class from test.class.php in ext/model/class. + return $this->testMisc->hello(); // After loading, can use $this->testMisc to call it. +} diff --git a/trunk/module/misc/ext/model/foo.php b/trunk/module/misc/ext/model/foo.php new file mode 100644 index 0000000000..57683477bb --- /dev/null +++ b/trunk/module/misc/ext/model/foo.php @@ -0,0 +1,4 @@ +public function foo() +{ + return 'foo'; +} diff --git a/trunk/module/misc/ext/model/hook/hello.test.php b/trunk/module/misc/ext/model/hook/hello.test.php new file mode 100644 index 0000000000..0fa90b6223 --- /dev/null +++ b/trunk/module/misc/ext/model/hook/hello.test.php @@ -0,0 +1,2 @@ +"; diff --git a/trunk/module/misc/ext/model/hook/hello.test2.php b/trunk/module/misc/ext/model/hook/hello.test2.php new file mode 100644 index 0000000000..d8ec452f50 --- /dev/null +++ b/trunk/module/misc/ext/model/hook/hello.test2.php @@ -0,0 +1,2 @@ +'; diff --git a/trunk/module/misc/ext/model/hook/hello2.start.php b/trunk/module/misc/ext/model/hook/hello2.start.php new file mode 100644 index 0000000000..1d1ecce293 --- /dev/null +++ b/trunk/module/misc/ext/model/hook/hello2.start.php @@ -0,0 +1,2 @@ +'; diff --git a/trunk/module/misc/ext/view/getsid.html.hook.php b/trunk/module/misc/ext/view/getsid.html.hook.php new file mode 100644 index 0000000000..95285b16e4 --- /dev/null +++ b/trunk/module/misc/ext/view/getsid.html.hook.php @@ -0,0 +1,5 @@ + +
      +output from the hook file. diff --git a/trunk/module/misc/ext/view/getsid.html.php b/trunk/module/misc/ext/view/getsid.html.php new file mode 100644 index 0000000000..22552fc5f5 --- /dev/null +++ b/trunk/module/misc/ext/view/getsid.html.php @@ -0,0 +1,4 @@ +
      +output from the ext view file:
      +ext config:misc);?> +ext lang:misc);?> diff --git a/trunk/module/misc/lang/en.php b/trunk/module/misc/lang/en.php new file mode 100644 index 0000000000..a6122919f6 --- /dev/null +++ b/trunk/module/misc/lang/en.php @@ -0,0 +1,47 @@ + + * @package misc + * @version $Id: English.php 824 2010-05-02 15:32:06Z wwccss $ + * @link http://www.zentao.net + */ +$lang->misc->common = 'Misc'; +$lang->misc->ping = 'Keep session'; + +$lang->misc->zentao->version = 'Version %s'; +$lang->misc->zentao->labels['about'] = 'About'; +$lang->misc->zentao->labels['support'] = 'Support'; +$lang->misc->zentao->labels['cowin'] = 'Help us'; +$lang->misc->zentao->labels['service'] = 'Services'; + +$lang->misc->zentao->about['proversion'] = 'Try pro version free!'; +$lang->misc->zentao->about['official'] = "Official site"; +$lang->misc->zentao->about['changelog'] = "Change log"; +$lang->misc->zentao->about['license'] = "License"; +$lang->misc->zentao->about['extension'] = "Extensions"; + +$lang->misc->zentao->support['vip'] = "Business"; +$lang->misc->zentao->support['manual'] = "Manual"; +$lang->misc->zentao->support['faq'] = "FAQ"; +$lang->misc->zentao->support['ask'] = "Ask"; +$lang->misc->zentao->support['qqgroup'] = "QQ Group"; + +$lang->misc->zentao->cowin['donate'] = "Donate"; +$lang->misc->zentao->cowin['reportbug'] = "Report bug"; +$lang->misc->zentao->cowin['feedback'] = "Feedback feature"; +$lang->misc->zentao->cowin['recommend'] = "Recommend"; +$lang->misc->zentao->cowin['cowinmore'] = "More..."; + +$lang->misc->zentao->service['zentaotrain']= 'ZenTao training'; +$lang->misc->zentao->service['scrumtrain'] = 'Agile training'; +$lang->misc->zentao->service['idc'] = 'ZenTao online'; +$lang->misc->zentao->service['custom'] = 'custom develop'; +$lang->misc->zentao->service['install'] = 'Install service'; +$lang->misc->zentao->service['fixissue'] = 'Issue support'; +$lang->misc->zentao->service['servicemore']= 'More...'; + +$lang->misc->copyright = "Copyright ©2009-2012 Nature EasySoft Network Tecnology Co.ltd, QingDao, China"; diff --git a/trunk/module/misc/lang/zh-cn.php b/trunk/module/misc/lang/zh-cn.php new file mode 100644 index 0000000000..c85ecd31e5 --- /dev/null +++ b/trunk/module/misc/lang/zh-cn.php @@ -0,0 +1,47 @@ + + * @package misc + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->misc->common = '杂项'; +$lang->misc->ping = '防超时'; + +$lang->misc->zentao->version = '版本%s'; +$lang->misc->zentao->labels['about'] = '关于禅道'; +$lang->misc->zentao->labels['support'] = '技术支持'; +$lang->misc->zentao->labels['cowin'] = '帮助我们'; +$lang->misc->zentao->labels['service'] = '服务列表'; + +$lang->misc->zentao->about['proversion'] = '免费试用专业版本'; +$lang->misc->zentao->about['official'] = "官方网站"; +$lang->misc->zentao->about['changelog'] = "版本历史"; +$lang->misc->zentao->about['license'] = "授权协议"; +$lang->misc->zentao->about['extension'] = "插件平台"; + +$lang->misc->zentao->support['vip'] = "商业技术支持"; +$lang->misc->zentao->support['manual'] = "用户手册"; +$lang->misc->zentao->support['faq'] = "常见问题"; +$lang->misc->zentao->support['ask'] = "官方问答"; +$lang->misc->zentao->support['qqgroup'] = "官方QQ群"; + +$lang->misc->zentao->cowin['donate'] = "捐助我们"; +$lang->misc->zentao->cowin['reportbug'] = "汇报Bug"; +$lang->misc->zentao->cowin['feedback'] = "反馈需求"; +$lang->misc->zentao->cowin['recommend'] = "推荐给朋友"; +$lang->misc->zentao->cowin['cowinmore'] = "更多方式..."; + +$lang->misc->zentao->service['zentaotrain']= '禅道使用培训'; +$lang->misc->zentao->service['scrumtrain'] = '敏捷开发培训'; +$lang->misc->zentao->service['idc'] = '禅道在线托管'; +$lang->misc->zentao->service['custom'] = '禅道定制开发'; +$lang->misc->zentao->service['install'] = '禅道安装服务'; +$lang->misc->zentao->service['fixissue'] = '禅道问题解决'; +$lang->misc->zentao->service['servicemore']= '更多服务...'; + +$lang->misc->copyright = "版权所有 ©2009-2012 青岛易软天创网络科技有限公司"; diff --git a/trunk/module/misc/lang/zh-tw.php b/trunk/module/misc/lang/zh-tw.php new file mode 100644 index 0000000000..c00a3bdacd --- /dev/null +++ b/trunk/module/misc/lang/zh-tw.php @@ -0,0 +1,47 @@ + + * @package misc + * @version $Id: zh-tw.php 3275 2012-07-02 12:09:36Z wwccss $ + * @link http://www.zentao.net + */ +$lang->misc->common = '雜項'; +$lang->misc->ping = '防超時'; + +$lang->misc->zentao->version = '版本%s'; +$lang->misc->zentao->labels['about'] = '關於禪道'; +$lang->misc->zentao->labels['support'] = '技術支持'; +$lang->misc->zentao->labels['cowin'] = '幫助我們'; +$lang->misc->zentao->labels['service'] = '服務列表'; + +$lang->misc->zentao->about['proversion'] = '免費試用專業版本'; +$lang->misc->zentao->about['official'] = "官方網站"; +$lang->misc->zentao->about['changelog'] = "版本歷史"; +$lang->misc->zentao->about['license'] = "授權協議"; +$lang->misc->zentao->about['extension'] = "插件平台"; + +$lang->misc->zentao->support['vip'] = "商業技術支持"; +$lang->misc->zentao->support['manual'] = "用戶手冊"; +$lang->misc->zentao->support['faq'] = "常見問題"; +$lang->misc->zentao->support['ask'] = "官方問答"; +$lang->misc->zentao->support['qqgroup'] = "官方QQ群"; + +$lang->misc->zentao->cowin['donate'] = "捐助我們"; +$lang->misc->zentao->cowin['reportbug'] = "彙報Bug"; +$lang->misc->zentao->cowin['feedback'] = "反饋需求"; +$lang->misc->zentao->cowin['recommend'] = "推薦給朋友"; +$lang->misc->zentao->cowin['cowinmore'] = "更多方式..."; + +$lang->misc->zentao->service['zentaotrain']= '禪道使用培訓'; +$lang->misc->zentao->service['scrumtrain'] = '敏捷開發培訓'; +$lang->misc->zentao->service['idc'] = '禪道在綫託管'; +$lang->misc->zentao->service['custom'] = '禪道定製開發'; +$lang->misc->zentao->service['install'] = '禪道安裝服務'; +$lang->misc->zentao->service['fixissue'] = '禪道問題解決'; +$lang->misc->zentao->service['servicemore']= '更多服務...'; + +$lang->misc->copyright = "版權所有 ©2009-2012 青島易軟天創網絡科技有限公司"; diff --git a/trunk/module/misc/model.php b/trunk/module/misc/model.php new file mode 100644 index 0000000000..8ac651eb2d --- /dev/null +++ b/trunk/module/misc/model.php @@ -0,0 +1,20 @@ + + * @package misc + * @version $Id$ + * @link http://www.zentao.net + */ +?> +'; + } +} diff --git a/trunk/module/misc/view/about.html.php b/trunk/module/misc/view/about.html.php new file mode 100644 index 0000000000..81ce12aa36 --- /dev/null +++ b/trunk/module/misc/view/about.html.php @@ -0,0 +1,13 @@ + + + + + + +
      + +

      misc->zentao->version, $config->version);?>

      +
      + +
      misc->copyright;?>
      +
      diff --git a/trunk/module/misc/view/checkupdate.html.php b/trunk/module/misc/view/checkupdate.html.php new file mode 100644 index 0000000000..32df38ec7e --- /dev/null +++ b/trunk/module/misc/view/checkupdate.html.php @@ -0,0 +1,9 @@ + + + style='color:white; font-size:13px; text-align:center'> + + + + + + diff --git a/trunk/module/misc/view/getsid.html.php b/trunk/module/misc/view/getsid.html.php new file mode 100644 index 0000000000..03dd38e949 --- /dev/null +++ b/trunk/module/misc/view/getsid.html.php @@ -0,0 +1,2 @@ + +Output from the main view file: diff --git a/trunk/module/misc/view/links.html.php b/trunk/module/misc/view/links.html.php new file mode 100755 index 0000000000..06a7176f25 --- /dev/null +++ b/trunk/module/misc/view/links.html.php @@ -0,0 +1,18 @@ + + misc->zentao->labels as $label) echo "";?> + misc->zentao->version); + unset($lang->misc->zentao->labels); + ?> + + misc->zentao as $groupItems):?> + + + +
      $label
      +
        + $label):?> +
      • + +
      +
      diff --git a/trunk/module/my/config.php b/trunk/module/my/config.php new file mode 100644 index 0000000000..7980e806cc --- /dev/null +++ b/trunk/module/my/config.php @@ -0,0 +1,7 @@ +my->editprofile->requiredFields = 'account,realname'; + +$config->my->dynamicCounts = 14; +$config->my->todoCounts = 10; +$config->my->taskCounts = 10; +$config->my->bugCounts = 10; diff --git a/trunk/module/my/control.php b/trunk/module/my/control.php new file mode 100644 index 0000000000..0fc35c4499 --- /dev/null +++ b/trunk/module/my/control.php @@ -0,0 +1,434 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +class my extends control +{ + /** + * Construct function. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('user'); + $this->loadModel('dept'); + $this->my->setMenu(); + } + + /** + * Index page, goto todo. + * + * @access public + * @return void + */ + public function index() + { + $account = $this->app->user->account; + + /* Get project and product stats. */ + $projectStats = $this->loadModel('project')->getProjectStats(); + $productStats = $this->loadModel('product')->getStats(); + + /* Set the dynamic pager. */ + $this->app->loadClass('pager', true); + $pager = new pager($recTotal = 1000, $this->config->my->dynamicCounts); // Init the $recTotal var, thus omit one sql query. + + $this->view->projectStats = $projectStats; + $this->view->productStats = $productStats; + $this->view->actions = $this->loadModel('action')->getDynamic('all', 'latest3days', 'date_desc', $pager); + $this->view->todos = $this->loadModel('todo')->getList('all', $account, 'wait, doing', $this->config->my->todoCounts); + $this->view->tasks = $this->loadModel('task')->getUserTasks($account, 'assignedTo', $this->config->my->taskCounts); + $this->view->bugs = $this->loadModel('bug')->getUserBugPairs($account, false, $this->config->my->bugCounts); + $this->view->users = $this->loadModel('user')->getPairs('noletter|withguest'); + $this->view->header->title = $this->lang->my->common; + + $this->display(); + } + + /** + * My todos. + * + * @param string $type + * @param string $account + * @param string $status + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function todo($type = 'today', $account = '', $status = 'all', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $uri = $this->app->getURI(true); + $this->session->set('todoList', $uri); + $this->session->set('bugList', $uri); + $this->session->set('taskList', $uri); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + /* The header and position. */ + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->todo; + $this->view->position[] = $this->lang->my->todo; + + /* Assign. */ + $this->view->dates = $this->loadModel('todo')->buildDateList(); + $this->view->todos = $this->todo->getList($type, $account, $status, 0, $pager); + $this->view->date = (int)$type == 0 ? date(DT_DATE1) : date(DT_DATE1, strtotime($type)); + $this->view->type = is_numeric($type) ? 'bydate' : $type; + $this->view->status = $status; + $this->view->account = $this->app->user->account; + $this->view->pager = $pager; + $this->view->importFuture = ($type == 'before' or $type == 'future' or $type == TODOMODEL::DAY_IN_FUTURE); + + $this->display(); + } + + /** + * My stories + + * @param string $type + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function story($type = 'assignedto', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $this->session->set('storyList', $this->app->getURI(true)); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + /* Assign. */ + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->story; + $this->view->position[] = $this->lang->my->story; + $this->view->stories = $this->loadModel('story')->getUserStories($this->app->user->account, $type, 'id_desc', $pager); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->type = $type; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * My tasks + * + * @param string $type + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function task($type = 'assignedto', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $this->session->set('taskList', $this->app->getURI(true)); + $this->session->set('storyList', $this->app->getURI(true)); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + /* Assign. */ + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->task; + $this->view->position[] = $this->lang->my->task; + $this->view->tabID = 'task'; + $this->view->tasks = $this->loadModel('task')->getUserTasks($this->app->user->account, $type, 0, $pager); + $this->view->type = $type; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->pager = $pager; + $this->display(); + } + + /** + * My bugs. + * + * @param string $type + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function bug($type = 'assigntome', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. load Lang. */ + $this->session->set('bugList', $this->app->getURI(true)); + $this->app->loadLang('bug'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + $bugs = array(); + if($type == 'assigntome') + { + $bugs = $this->dao->select('t1.*') + ->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t2.deleted')->eq(0) + ->andWhere('t1.deleted')->eq(0) + ->andWhere('t1.assignedTo')->eq($this->app->user->account) + ->orderBy('t1.id_desc')->page($pager)->fetchAll(); + } + elseif($type == 'openedbyme') + { + $bugs = $this->dao->findByOpenedBy($this->app->user->account)->from(TABLE_BUG) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + elseif($type == 'resolvedbyme') + { + $bugs = $this->dao->findByResolvedBy($this->app->user->account)->from(TABLE_BUG) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + elseif($type == 'closedbyme') + { + $bugs = $this->dao->findByClosedBy($this->app->user->account)->from(TABLE_BUG) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + + /* Save bugIDs session for get the pre and next bug. */ + $bugIDs = ''; + foreach($bugs as $bug) $bugIDs .= ',' . $bug->id; + $this->session->set('bugIDs', $bugIDs . ','); + + /* assign. */ + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->bug; + $this->view->position[] = $this->lang->my->bug; + $this->view->bugs = $bugs; + $this->view->users = $this->user->getPairs('noletter'); + $this->view->tabID = 'bug'; + $this->view->type = $type; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * My test task. + * + * @access public + * @return void + */ + public function testtask() + { + /* Save session. */ + $this->session->set('testtaskList', $this->app->getURI(true)); + + $this->app->loadLang('testcase'); + + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->testTask; + $this->view->position[] = $this->lang->my->testTask; + $this->view->tasks = $this->loadModel('testtask')->getByUser($this->app->user->account); + + $this->display(); + + } + + /** + * My test case. + * + * @param string $type + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function testcase($type = 'assigntome', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session, load lang. */ + $this->session->set('caseList', $this->app->getURI(true)); + $this->app->loadLang('testcase'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + $cases = array(); + if($type == 'assigntome') + { + $cases = $this->dao->select('t1.assignedTo AS assignedTo, t2.*')->from(TABLE_TESTRUN)->alias('t1') + ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id') + ->leftJoin(TABLE_TESTTASK)->alias('t3')->on('t1.task = t3.id') + ->Where('t1.assignedTo')->eq($this->app->user->account) + ->andWhere('t1.status')->ne('done') + ->andWhere('t3.status')->ne('done') + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + elseif($type == 'donebyme') + { + $cases = $this->dao->select('t1.assignedTo AS assignedTo, t2.*')->from(TABLE_TESTRUN)->alias('t1') + ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id') + ->Where('t1.assignedTo')->eq($this->app->user->account) + ->andWhere('t1.status')->eq('done') + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + elseif($type == 'openedbyme') + { + $cases = $this->dao->findByOpenedBy($this->app->user->account)->from(TABLE_CASE) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase'); + + /* Assign. */ + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->testCase; + $this->view->position[] = $this->lang->my->testCase; + $this->view->cases = $cases; + $this->view->users = $this->user->getPairs('noletter'); + $this->view->tabID = 'test'; + $this->view->type = $type; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * My projects. + * + * @access public + * @return void + */ + public function project() + { + $this->app->loadLang('project'); + + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->myProject; + $this->view->position[] = $this->lang->my->myProject; + $this->view->tabID = 'project'; + $this->view->projects = @array_reverse($this->user->getProjects($this->app->user->account)); + + $this->display(); + } + + /** + * Edit profile + * + * @access public + * @return void + */ + public function editProfile() + { + if($this->app->user->account == 'guest') die(js::alert('guest') . js::locate('back')); + if(!empty($_POST)) + { + $this->user->update($this->app->user->id); + if(dao::isError()) die(js::error(dao::getError())); + die(js::locate($this->createLink('my', 'profile'), 'parent')); + } + + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->editProfile; + $this->view->position[] = $this->lang->my->editProfile; + $this->view->user = $this->user->getById($this->app->user->id); + + $this->display(); + } + + /** + * Change password + * + * @access public + * @return void + */ + public function changePassword() + { + if($this->app->user->account == 'guest') die(js::alert('guest') . js::locate('back')); + if(!empty($_POST)) + { + $this->user->updatePassword($this->app->user->id); + if(dao::isError()) die(js::error(dao::getError())); + die(js::locate($this->createLink('my', 'profile'), 'parent')); + } + + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->changePassword; + $this->view->position[] = $this->lang->my->changePassword; + $this->view->user = $this->user->getById($this->app->user->id); + + $this->display(); + } + + /** + * View my profile. + * + * @access public + * @return void + */ + public function profile() + { + if($this->app->user->account == 'guest') die(js::alert('guest') . js::locate('back')); + $user = $this->user->getById($this->app->user->account); + $deptPath = $this->dept->getParents($user->dept); + $this->view->deptPath = $deptPath; + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->profile; + $this->view->position[] = $this->lang->my->profile; + $this->view->user = $this->user->getById($this->app->user->id); + $this->display(); + } + + /** + * My dynamic. + * + * @param string $type + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function dynamic($type = 'today', $orderBy = 'date_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $uri = $this->app->getURI(true); + $this->session->set('productList', $uri); + $this->session->set('productPlanList', $uri); + $this->session->set('releaseList', $uri); + $this->session->set('storyList', $uri); + $this->session->set('projectList', $uri); + $this->session->set('taskList', $uri); + $this->session->set('buildList', $uri); + $this->session->set('bugList', $uri); + $this->session->set('caseList', $uri); + $this->session->set('testtaskList', $uri); + + /* Set the pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; + + /* The header and position. */ + $this->view->header->title = $this->lang->my->common . $this->lang->colon . $this->lang->my->dynamic; + $this->view->position[] = $this->lang->my->dynamic; + + /* Assign. */ + $this->view->type = $type; + $this->view->actions = $this->loadModel('action')->getDynamic($this->app->user->account, $type, $orderBy, $pager); + $this->display(); + } + +} diff --git a/trunk/module/my/css/index.css b/trunk/module/my/css/index.css new file mode 100644 index 0000000000..3525d213d5 --- /dev/null +++ b/trunk/module/my/css/index.css @@ -0,0 +1,8 @@ +.block {border:1px solid #efefef; height:225px;} +.linkbox1{height:180px; overflow-y:auto} +.linkbox2{height:225px; overflow-y:auto} + +#row1{margin-bottom:20px} +#row2{margin-bottom:10px} +#row1 .table-1, #row1 caption{border:none} +#row2 .table-1, #row2 caption{border:none} diff --git a/trunk/module/my/js/index.js b/trunk/module/my/js/index.js new file mode 100644 index 0000000000..a2c933d982 --- /dev/null +++ b/trunk/module/my/js/index.js @@ -0,0 +1,17 @@ +$(function() +{ + /* Set the heights of every block to keep them same height. */ + projectBoxHeight = $('#projectbox').height(); + productBoxHeight = $('#productbox').height(); + if(projectBoxHeight < 180) $('#projectbox').css('height', 180); + if(productBoxHeight < 180) $('#productbox').css('height', 180); + + row2Height = $('#row2').height() - 10; + row2Height = row2Height > 200 ? row2Height : 200; + $('#row2 .block').each(function(){$(this).css('height', row2Height);}) + + $('.projectline').each(function() + { + $(this).sparkline('html', {height:'25px'}); + }) +}); diff --git a/trunk/module/my/js/task.js b/trunk/module/my/js/task.js new file mode 100644 index 0000000000..c397fe8e86 --- /dev/null +++ b/trunk/module/my/js/task.js @@ -0,0 +1,4 @@ +$(function() +{ + $('#' + type + 'Tab').addClass('active'); +}) diff --git a/trunk/module/my/js/testcase.js b/trunk/module/my/js/testcase.js new file mode 100644 index 0000000000..41a155209f --- /dev/null +++ b/trunk/module/my/js/testcase.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + $('#modulemenu li:eq(5)').addClass('active'); +}) diff --git a/trunk/module/my/js/todo.js b/trunk/module/my/js/todo.js new file mode 100644 index 0000000000..f3f2ffe72c --- /dev/null +++ b/trunk/module/my/js/todo.js @@ -0,0 +1,21 @@ +function changeDate(date) +{ + date = date.replace(/\-/g, ''); + link = createLink('my', 'todo', 'type=' + date); + location.href=link; +} + +/** + * Change form action. + * + * @param formName $formName + * @param actionName $actionName + * @param actionLink $actionLink + * @access public + * @return void + */ +function changeAction(formName, actionName, actionLink) +{ + $('#' + formName).attr('action', actionLink).submit(); +} + diff --git a/trunk/module/my/lang/en.php b/trunk/module/my/lang/en.php new file mode 100644 index 0000000000..98da737aa0 --- /dev/null +++ b/trunk/module/my/lang/en.php @@ -0,0 +1,39 @@ +my->common = 'Dashboard'; + +/* Methods. */ +$lang->my->index = 'Index'; +$lang->my->todo = 'Todo'; +$lang->my->task = 'Task'; +$lang->my->bug = 'Bug'; +$lang->my->testTask = 'Test task'; +$lang->my->testCase = 'Test case'; +$lang->my->story = 'Story'; +$lang->my->myProject = 'Project'; +$lang->my->team = 'Team'; +$lang->my->profile = 'Profile'; +$lang->my->dynamic = 'Dynamic'; +$lang->my->editProfile = 'Edit profile'; +$lang->my->changePassword = 'Change password'; + +$lang->my->taskMenu->assignedToMe = 'To me'; +$lang->my->taskMenu->openedByMe = 'My opened'; +$lang->my->taskMenu->finishedByMe = 'My finished'; +$lang->my->taskMenu->closedByMe = 'My closed'; +$lang->my->taskMenu->canceledByMe = 'My canceled'; + +$lang->my->storyMenu->assignedToMe = 'To me'; +$lang->my->storyMenu->openedByMe = 'My opened'; +$lang->my->storyMenu->reviewedByMe = 'My reviewed'; +$lang->my->storyMenu->closedByMe = 'My closed'; + +$lang->my->home->latest = 'Latest actions:'; +$lang->my->home->action = "%s, %s %s %s %s."; +$lang->my->home->projects = 'Project'; +$lang->my->home->products = 'Product'; +$lang->my->home->projectHome = 'Project home'; +$lang->my->home->productHome = 'Product home'; +$lang->my->home->noProjectsTip = "No running projects yet, you can create one now"; +$lang->my->home->noProductsTip = "No products yet, creae one now"; +$lang->my->home->help = "Help Book"; +$lang->my->home->otherNoTip = " or modify existings to 'running'."; diff --git a/trunk/module/my/lang/zh-cn.php b/trunk/module/my/lang/zh-cn.php new file mode 100644 index 0000000000..4447e1d756 --- /dev/null +++ b/trunk/module/my/lang/zh-cn.php @@ -0,0 +1,39 @@ +my->common = '我的地盘'; + +/* 方法列表。*/ +$lang->my->index = '首页'; +$lang->my->todo = '我的TODO'; +$lang->my->task = '我的任务'; +$lang->my->bug = '我的Bug'; +$lang->my->testTask = '我的测试任务'; +$lang->my->testCase = '我的测试用例'; +$lang->my->story = '我的需求'; +$lang->my->myProject = '我的项目'; +$lang->my->team = '我的团队'; +$lang->my->profile = '我的档案'; +$lang->my->dynamic = '我的动态'; +$lang->my->editProfile = '更新信息'; +$lang->my->changePassword = '修改密码'; + +$lang->my->taskMenu->assignedToMe = '指派给我'; +$lang->my->taskMenu->openedByMe = '由我创建'; +$lang->my->taskMenu->finishedByMe = '由我完成'; +$lang->my->taskMenu->closedByMe = '由我关闭'; +$lang->my->taskMenu->canceledByMe = '由我取消'; + +$lang->my->storyMenu->assignedToMe = '指派给我'; +$lang->my->storyMenu->openedByMe = '由我创建'; +$lang->my->storyMenu->reviewedByMe = '由我评审'; +$lang->my->storyMenu->closedByMe = '由我关闭'; + +$lang->my->home->latest = '最新动态'; +$lang->my->home->action = "%s, %s %s %s %s。"; +$lang->my->home->projects = '项目'; +$lang->my->home->products = '产品'; +$lang->my->home->projectHome = '访问项目主页'; +$lang->my->home->productHome = '访问产品主页'; +$lang->my->home->noProjectsTip = "目前还没有进行中的项目,创建一个项目   ,"; +$lang->my->home->noProductsTip = "目前还没有产品,创建一个产品   。"; +$lang->my->home->help = "帮助文档"; +$lang->my->home->otherNoTip = "或者修改已有的项目,将其状态改为“进行中 ”。"; diff --git a/trunk/module/my/lang/zh-tw.php b/trunk/module/my/lang/zh-tw.php new file mode 100644 index 0000000000..47657bfb81 --- /dev/null +++ b/trunk/module/my/lang/zh-tw.php @@ -0,0 +1,39 @@ +my->common = '我的地盤'; + +/* 方法列表。*/ +$lang->my->index = '首頁'; +$lang->my->todo = '我的TODO'; +$lang->my->task = '我的任務'; +$lang->my->bug = '我的Bug'; +$lang->my->testTask = '我的測試任務'; +$lang->my->testCase = '我的測試用例'; +$lang->my->story = '我的需求'; +$lang->my->myProject = '我的項目'; +$lang->my->team = '我的團隊'; +$lang->my->profile = '我的檔案'; +$lang->my->dynamic = '我的動態'; +$lang->my->editProfile = '更新信息'; +$lang->my->changePassword = '修改密碼'; + +$lang->my->taskMenu->assignedToMe = '指派給我'; +$lang->my->taskMenu->openedByMe = '由我創建'; +$lang->my->taskMenu->finishedByMe = '由我完成'; +$lang->my->taskMenu->closedByMe = '由我關閉'; +$lang->my->taskMenu->canceledByMe = '由我取消'; + +$lang->my->storyMenu->assignedToMe = '指派給我'; +$lang->my->storyMenu->openedByMe = '由我創建'; +$lang->my->storyMenu->reviewedByMe = '由我評審'; +$lang->my->storyMenu->closedByMe = '由我關閉'; + +$lang->my->home->latest = '最新動態'; +$lang->my->home->action = "%s, %s %s %s %s。"; +$lang->my->home->projects = '項目'; +$lang->my->home->products = '產品'; +$lang->my->home->projectHome = '訪問項目主頁'; +$lang->my->home->productHome = '訪問產品主頁'; +$lang->my->home->noProjectsTip = "目前還沒有進行中的項目,創建一個項目   ,"; +$lang->my->home->noProductsTip = "目前還沒有產品,創建一個產品   。"; +$lang->my->home->help = "幫助文檔"; +$lang->my->home->otherNoTip = "或者修改已有的項目,將其狀態改為“進行中 ”。"; diff --git a/trunk/module/my/model.php b/trunk/module/my/model.php new file mode 100644 index 0000000000..dedad78817 --- /dev/null +++ b/trunk/module/my/model.php @@ -0,0 +1,26 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> +lang->my->menu->account = sprintf($this->lang->my->menu->account, $this->app->user->realname); + } +} diff --git a/trunk/module/my/view/blockbugs.html.php b/trunk/module/my/view/blockbugs.html.php new file mode 100644 index 0000000000..08b1c41c56 --- /dev/null +++ b/trunk/module/my/view/blockbugs.html.php @@ -0,0 +1,14 @@ +
      + + + $bugTitle) + { + echo ""; + } + ?> +
      +
      my->bug;?>
      +
      createLink('my', 'bug'), $lang->more . "");?>
      +
      " . "#$bugID " . html::a($this->createLink('bug', 'view', "id=$bugID"), $bugTitle) . "
      +
      diff --git a/trunk/module/my/view/blockdynamic.html.php b/trunk/module/my/view/blockdynamic.html.php new file mode 100644 index 0000000000..e108492273 --- /dev/null +++ b/trunk/module/my/view/blockdynamic.html.php @@ -0,0 +1,24 @@ +
      + + + product == 0 and $action->project == 0) $canView = true; + if(isset($productStats['products'][$action->product]) or isset($projectStats['projects'][$action->project])) $canView = true; + + if(!$canView) continue; + $user = isset($users[$action->actor]) ? $users[$action->actor] : $action->actor; + if($action->action == 'login' or $action->action == 'logout') $action->objectName = ''; + echo ""; + } + ?> +
      +
      my->home->latest;?>
      +
      more . "");?>
      +
      "; + printf($lang->my->home->action, $action->date, $user, $action->actionLabel, $action->objectLabel, $action->objectLink, $action->objectName); + echo "
      +
      diff --git a/trunk/module/my/view/blockproducts.html.php b/trunk/module/my/view/blockproducts.html.php new file mode 100644 index 0000000000..031aaa315a --- /dev/null +++ b/trunk/module/my/view/blockproducts.html.php @@ -0,0 +1,50 @@ +
      + + + + + + +
      my->home->products;?>
      + + + + + + + + + +
      my->home->noProductsTip, $this->createLink('product', 'create'));?>
      my->home->help; ?>
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      product->name;?>story->statusList['active'] . $lang->story->common;?>story->statusList['changed'] . $lang->story->common;?>story->statusList['draft'] . $lang->story->common;?>story->statusList['closed'] . $lang->story->common;?>product->plans;?>product->releases;?>product->bugs;?>bug->unResolved;?>bug->assignToNull;?>
      createLink('product', 'view', 'product=' . $product->id), $product->name);?>stories['active']?>stories['changed']?>stories['draft']?>stories['closed']?>plans?>releases?>bugs?>unResolved?>assignToNull?>
      + +
      diff --git a/trunk/module/my/view/blockprojects.html.php b/trunk/module/my/view/blockprojects.html.php new file mode 100644 index 0000000000..8df63f588f --- /dev/null +++ b/trunk/module/my/view/blockprojects.html.php @@ -0,0 +1,53 @@ +
      + + + + + + +
      my->home->projects;?>
      + + + + + + + + + + + + + +
      my->home->noProjectsTip, $this->createLink('project', 'create'));?>
      my->home->otherNoTip;?>
      my->home->help; ?>
      +
      + + + + + + + + + + + + + + + + + + + + + + + + +
      project->name;?>project->end;?>statusAB;?>project->totalEstimate;?>project->totalConsumed;?>project->totalLeft;?>project->progess;?>project->burn;?>
      createLink('project', 'task', 'project=' . $project->id), $project->name);?>end;?>project->statusList[$project->status];?>hours->totalEstimate;?>hours->totalConsumed;?>hours->totalLeft;?> + hours->progress):?>hours->progress;?> height='13' text-align: /> + hours->progress;?>% + burns);?>'>
      + +
      diff --git a/trunk/module/my/view/blocktasks.html.php b/trunk/module/my/view/blocktasks.html.php new file mode 100644 index 0000000000..51a78ff718 --- /dev/null +++ b/trunk/module/my/view/blocktasks.html.php @@ -0,0 +1,14 @@ +
      + + + "; + } + ?> +
      +
      my->task;?>
      +
      createLink('my', 'task'), $lang->more . "");?>
      +
      " . "#$task->id " . html::a($this->createLink('task', 'view', "id=$task->id"), $task->name) . "
      +
      diff --git a/trunk/module/my/view/blocktodoes.html.php b/trunk/module/my/view/blocktodoes.html.php new file mode 100644 index 0000000000..632bcb2ff6 --- /dev/null +++ b/trunk/module/my/view/blocktodoes.html.php @@ -0,0 +1,14 @@ +
      + + + "; + } + ?> +
      +
      my->todo;?>
      +
      createLink('my', 'todo'), $lang->more . "");?>
      +
      " . "#$todo->id " . html::a($this->createLink('todo', 'view', "id=$todo->id"), $todo->name) . "
      +
      diff --git a/trunk/module/my/view/bug.html.php b/trunk/module/my/view/bug.html.php new file mode 100644 index 0000000000..a0e514b260 --- /dev/null +++ b/trunk/module/my/view/bug.html.php @@ -0,0 +1,65 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      +
      + " . html::a(inlink('bug', "type=assigntome"), $lang->bug->assignToMe) . ""; + echo "" . html::a(inlink('bug', "type=openedbyme"), $lang->bug->openedByMe) . ""; + echo "" . html::a(inlink('bug', "type=resolvedbyme"), $lang->bug->resolvedByMe) . ""; + echo "" . html::a(inlink('bug', "type=closedbyme"), $lang->bug->closedByMe) . ""; + ?> +
      +
      +recTotal}&recPerPage={$pager->recPerPage}"; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      idAB;?>bug->severityAB;?>priAB;?>typeAB;?>bug->title;?>openedByAB;?>bug->resolvedByAB;?>bug->resolutionAB;?>actions;?>
      createLink('bug', 'view', "bugID=$bug->id"), $bug->id, '_blank');?>bug->severityList[$bug->severity]?>bug->priList[$bug->pri]?>bug->typeList[$bug->type]?>createLink('bug', 'view', "bugID=$bug->id"), $bug->title);?>openedBy];?>resolvedBy];?>bug->resolutionList[$bug->resolution];?> + id"; + common::printIcon('bug', 'resolve', $params, $bug, 'list'); + common::printIcon('bug', 'close', $params, $bug, 'list'); + common::printIcon('bug', 'edit', $params, '', 'list'); + ?> +
      show();?>
      + + diff --git a/trunk/module/my/view/changepassword.html.php b/trunk/module/my/view/changepassword.html.php new file mode 100755 index 0000000000..3abefc8917 --- /dev/null +++ b/trunk/module/my/view/changepassword.html.php @@ -0,0 +1,34 @@ + + * @package user + * @version $Id: editprofile.html.php 2605 2012-02-21 07:22:58Z wwccss $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + +
      my->changePassword;?>
      user->account;?>account . html::hidden('account',$user->account);?>
      user->password;?>
      user->password2;?>
      +
      + diff --git a/trunk/module/my/view/dynamic.html.php b/trunk/module/my/view/dynamic.html.php new file mode 100644 index 0000000000..3f890595e2 --- /dev/null +++ b/trunk/module/my/view/dynamic.html.php @@ -0,0 +1,55 @@ +dynamic view file of dashboard module of ZenTaoPMS. + * + * @copyright Copyright 2009-2012 青岛易软天创网络科技有限公司 (QingDao Nature Easy Soft Network Technology Co,LTD www.cnezsoft.com) + * @license LGPL (http://www.gnu.org/licenses/lgpl.html) + * @author Chunsheng Wang + * @package dashboard + * @version $Id: action->dynamic.html.php 1477 2011-03-01 15:25:50Z wwccss $ + * @link http://www.zentao.net + */ +?> + + +
      + ' . html::a(inlink('dynamic', "type=today"), $lang->action->dynamic->today) . ''; + echo '' . html::a(inlink('dynamic', "type=yesterday"), $lang->action->dynamic->yesterday) . ''; + echo '' . html::a(inlink('dynamic', "type=twodaysago"), $lang->action->dynamic->twoDaysAgo) . ''; + echo '' . html::a(inlink('dynamic', "type=thisweek"), $lang->action->dynamic->thisWeek) . ''; + echo '' . html::a(inlink('dynamic', "type=lastweek"), $lang->action->dynamic->lastWeek) . ''; + echo '' . html::a(inlink('dynamic', "type=thismonth"), $lang->action->dynamic->thisMonth) . ''; + echo '' . html::a(inlink('dynamic', "type=lastmonth"), $lang->action->dynamic->lastMonth) . ''; + echo '' . html::a(inlink('dynamic', "type=all"), $lang->action->dynamic->all) . ''; + ?> +
      + + + + + + + + + + + + + + + objectType == 'case' ? 'testcase' : $action->objectType;?> + + + + + + + + + + + +
      action->date;?> action->actor;?>action->action;?> action->objectType;?> idAB;?>action->objectName;?>
      date;?>user->realname;?>actionLabel;?>action->objectTypes[$action->objectType];?>objectID;?>objectLink, $action->objectName);?>
      show();?>
      + + diff --git a/trunk/module/my/view/editprofile.html.php b/trunk/module/my/view/editprofile.html.php new file mode 100644 index 0000000000..3a2d024ea2 --- /dev/null +++ b/trunk/module/my/view/editprofile.html.php @@ -0,0 +1,100 @@ + + * @package user + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      my->editProfile;?>
      user->account;?>account . html::hidden('account',$user->account);?>
      user->realname;?>realname);?>
      user->commiter;?>commiter);?>
      user->email;?>email);?>
      user->gender;?>user->genderList, $user->gender);?>
      user->password;?>
      user->password2;?>
      user->birthyear;?>birthday,"class='date'");?>
      user->join;?>account, "readonly"); + echo $user->join; + echo html::hidden('join',$user->join); + ?> +
      user->msn;?>msn);?>
      user->qq;?>qq);?>
      user->yahoo;?>yahoo);?>
      user->gtalk;?>gtalk);?>
      user->wangwang;?>wangwang);?>
      user->mobile;?>mobile);?>
      user->phone;?>phone);?>
      user->address;?>address);?>
      user->zipcode;?>zipcode);?>
      +
      + diff --git a/trunk/module/my/view/index.html.php b/trunk/module/my/view/index.html.php new file mode 100644 index 0000000000..7bdd699938 --- /dev/null +++ b/trunk/module/my/view/index.html.php @@ -0,0 +1,32 @@ + + * @package ZenTaoPMS + * @version $Id: index.html.php 1947 2011-06-29 11:58:03Z wwccss $ + */ +?> + + + +version);?> + + + + + +
      +
      + +
      + + + + + + +
      + diff --git a/trunk/module/my/view/profile.html.php b/trunk/module/my/view/profile.html.php new file mode 100644 index 0000000000..1c615e7769 --- /dev/null +++ b/trunk/module/my/view/profile.html.php @@ -0,0 +1,120 @@ + + * @package my + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      my->profile;?>
      user->dept;?> + $dept) + { + if($dept->name) echo $dept->name; + if(isset($deptPath[$key + 1])) echo $lang->arrow; + } + } + ?> +
      user->account;?>account;?>
      user->realname;?>realname;?>
      user->commiter;?>commiter;?>
      user->email;?>email;?>
      user->join;?>join;?>
      user->visits;?>visits;?>
      user->ip;?>ip;?>
      user->last;?>last;?>
      user->msn;?>msn;?>
      user->qq;?>qq;?>
      user->yahoo;?>yahoo;?>
      user->gtalk;?>gtalk;?>
      user->wangwang;?>wangwang;?>
      user->mobile;?>mobile;?>
      user->phone;?>phone;?>
      user->address;?>address;?>
      user->zipcode;?>zipcode;?>
      + createLink('my', 'editprofile'), $lang->user->editProfile); + echo html::a($this->createLink('user', 'logout'), $lang->logout); + ?> +
      + diff --git a/trunk/module/my/view/project.html.php b/trunk/module/my/view/project.html.php new file mode 100644 index 0000000000..e05ec7cf72 --- /dev/null +++ b/trunk/module/my/view/project.html.php @@ -0,0 +1,46 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + createLink('project', 'browse', "projectID=$project->id");?> + + + + + + + + + + + + + +
      idAB;?>project->code;?>project->name;?>project->begin;?>project->end;?>statusAB;?>team->role;?>team->join;?>team->hours;?>
      id);?>code;?>name);?>begin;?>end;?>project->statusList[$project->status];?>role;?>join;?>hours;?>
      + diff --git a/trunk/module/my/view/story.html.php b/trunk/module/my/view/story.html.php new file mode 100644 index 0000000000..43b744b5bf --- /dev/null +++ b/trunk/module/my/view/story.html.php @@ -0,0 +1,66 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      +
      + " . html::a(inlink('story', "type=assignedto"), $lang->my->storyMenu->assignedToMe) . ""; + echo "" . html::a(inlink('story', "type=openedby"), $lang->my->storyMenu->openedByMe) . ""; + echo "" . html::a(inlink('story', "type=reviewedby"), $lang->my->storyMenu->reviewedByMe) . ""; + echo "" . html::a(inlink('story', "type=closedby"), $lang->my->storyMenu->closedByMe) . ""; + ?> +
      +
      + + + + + + + + + + + + + + + + + $story):?> + createLink('story', 'view', "id=$story->id");?> + + + + + + + + + + + + + + + +
      idAB;?>priAB;?>story->product;?>story->title;?>story->plan;?>openedByAB;?>story->estimateAB;?>statusAB;?>story->stageAB;?>actions;?>
      id));?>pri;?>productTitle;?>title);?>planTitle;?>openedBy];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?> + id", $story, 'list'); + common::printIcon('story', 'review', "storyID=$story->id", $story, 'list'); + common::printIcon('story', 'close', "storyID=$story->id", $story, 'list'); + ?> +
      show();?>
      + + diff --git a/trunk/module/my/view/task.html.php b/trunk/module/my/view/task.html.php new file mode 100644 index 0000000000..150a6a63ce --- /dev/null +++ b/trunk/module/my/view/task.html.php @@ -0,0 +1,87 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + +
      +
      + " . html::a(inlink('task', "type=assignedto"), $lang->my->taskMenu->assignedToMe) . ""; + echo "" . html::a(inlink('task', "type=openedby"), $lang->my->taskMenu->openedByMe) . ""; + echo "" . html::a(inlink('task', "type=finishedby"), $lang->my->taskMenu->finishedByMe) . ""; + echo "" . html::a(inlink('task', "type=closedby"), $lang->my->taskMenu->closedByMe) . ""; + echo "" . html::a(inlink('task', "type=canceledby"), $lang->my->taskMenu->canceledByMe) . ""; + ?> +
      +
      + + +
      '> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      idAB;?>priAB;?>task->project;?>task->name;?>task->estimateAB;?>task->consumedAB;?>task->leftAB;?>task->deadlineAB;?>statusAB;?>openedByAB;?>actions;?>
      + + createLink('task', 'view', "taskID=$task->id"), sprintf('%03d', $task->id));?> + task->priList[$task->pri];?>createLink('project', 'browse', "projectid=$task->projectID"), $task->projectName);?> + createLink('task', 'view', "taskID=$task->id"), $task->name);?>estimate;?>consumed;?>left;?>delay)) echo 'delayed';?>>deadline, 0, 4) > 0) echo $task->deadline;?>task->statusList[$task->status];?>openedBy];?> + id", $task, 'list'); + common::printIcon('task', 'finish', "taskID=$task->id", $task, 'list'); + common::printIcon('task', 'close', "taskID=$task->id", $task, 'list'); + common::printIcon('task', 'activate', "taskID=$task->id", $task, 'list'); + ?> +
      + +
      close)?>
      + + show();?> +
      +';?> + diff --git a/trunk/module/my/view/team.html.php b/trunk/module/my/view/team.html.php new file mode 100644 index 0000000000..95c5ad5ce6 --- /dev/null +++ b/trunk/module/my/view/team.html.php @@ -0,0 +1,14 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + diff --git a/trunk/module/my/view/testcase.html.php b/trunk/module/my/view/testcase.html.php new file mode 100644 index 0000000000..6e61b3a28f --- /dev/null +++ b/trunk/module/my/view/testcase.html.php @@ -0,0 +1,55 @@ + + * @package dashboard + * @version $Id: test.html.php 1191 2010-11-13 07:30:35Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + +
      +
      + " . html::a($this->createLink('my', 'testtask'), $lang->my->testTask) . ""; + echo "" . html::a($this->createLink('my', 'testcase', "type=assigntome"), $lang->testcase->assignToMe) . ""; + //echo "" . html::a($this->createLink('my', 'testcase', "type=donebyme"), $lang->testcase->doneByMe) . ""; + echo "" . html::a($this->createLink('my', 'testcase', "type=openedbyme"), $lang->testcase->openedByMe) . ""; + ?> +
      +
      + +recTotal}&recPerPage={$pager->recPerPage}"; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
      idAB;?>priAB;?>testcase->title;?> typeAB;?> openedByAB;?>statusAB;?>actions;?>
      createLink('testcase', 'view', "testcaseID=$case->id"), sprintf('%03d', $case->id));?>pri?>createLink('testcase', 'view', "testcaseID=$case->id"), $case->title);?>testcase->typeList[$case->type];?>openedBy];?>testcase->statusList[$case->status];?>id", '', 'list');?>
      show();?>
      + + diff --git a/trunk/module/my/view/testtask.html.php b/trunk/module/my/view/testtask.html.php new file mode 100644 index 0000000000..25013a66ff --- /dev/null +++ b/trunk/module/my/view/testtask.html.php @@ -0,0 +1,60 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      +
      + " . html::a($this->createLink('my', 'testtask'), $lang->my->testTask) . ""; + echo "" . html::a($this->createLink('my', 'testcase', "type=assigntome"), $lang->testcase->assignToMe) . ""; + //echo "" . html::a($this->createLink('my', 'testcase', "type=donebyme"), $lang->testcase->doneByMe) . ""; + echo "" . html::a($this->createLink('my', 'testcase', "type=openedbyme"), $lang->testcase->openedByMe) . ""; + ?> +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      idAB;?>testtask->name;?>testtask->project;?>testtask->build;?>testtask->begin;?>testtask->end;?>statusAB;?>actions;?>
      createLink('testtask', 'view', "taskID=$task->id"), sprintf('%03d', $task->id));?>createLink('testtask', 'view', "taskID=$task->id"), $task->name);?>projectName?>build == 'trunk' ? print('Trunk') : print(html::a($this->createLink('build', 'view', "buildID=$task->build"), $task->buildName));?>begin?>end?>testtask->statusList[$task->status];?> + id", $lang->testtask->cases); + common::printIcon('testtask', 'edit', "taskID=$task->id", '', 'list'); + common::printIcon('testtask', 'delete', "taskID=$task->id", '', 'list', '', 'hiddenwin'); + ?> +
      + + diff --git a/trunk/module/my/view/todo.html.php b/trunk/module/my/view/todo.html.php new file mode 100644 index 0000000000..256d95c520 --- /dev/null +++ b/trunk/module/my/view/todo.html.php @@ -0,0 +1,104 @@ + + * @package dashboard + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + +
      +
      + ' . html::a(inlink('todo', "date=today"), $lang->todo->todayTodos) . ''; + echo '' . html::a(inlink('todo', "date=yesterday"), $lang->todo->yesterdayTodos). ''; + echo '' . html::a(inlink('todo', "date=thisweek"), $lang->todo->thisWeekTodos) . ''; + echo '' . html::a(inlink('todo', "date=lastweek"), $lang->todo->lastWeekTodos) . ''; + echo '' . html::a(inlink('todo', "date=thismonth"), $lang->todo->thismonthTodos). ''; + echo '' . html::a(inlink('todo', "date=lastmonth"), $lang->todo->lastmonthTodos). ''; + echo '' . html::a(inlink('todo', "date=thisseason"),$lang->todo->thisseasonTodos).''; + echo '' . html::a(inlink('todo', "date=thisyear"), $lang->todo->thisyearTodos) . ''; + echo '' . html::a(inlink('todo', "date=future"), $lang->todo->futureTodos) . ''; + echo '' . html::a(inlink('todo', "date=all"), $lang->todo->allDaysTodos) . ''; + echo '' . html::a(inlink('todo', "date=before&account={$app->user->account}&status=undone"), $lang->todo->allUndone) . ''; + echo "" . html::input('date', $date, "class='w-date date' onchange=changeDate(this.value)") . ''; + ?> + +
      +
      + +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      idAB;?>todo->date;?>todo->type;?>priAB;?>todo->name;?>todo->beginAB;?>todo->endAB;?>todo->status;?>actions;?>
      + + + + id; ?> + date == '2030-01-01' ? $lang->todo->dayInFuture : $todo->date;?>todo->typeList->{$todo->type};?>pri;?>createLink('todo', 'view', "id=$todo->id&from=my"), $todo->name);?>begin;?>end;?>todo->statusList[$todo->status];?> + createLink('todo', 'mark', "id=$todo->id&status=$todo->status"), $lang->todo->{'mark'.ucfirst($todo->status)}, 'hiddenwin'); + common::printIcon('todo', 'edit', "id=$todo->id", '', 'list'); + common::printIcon('todo', 'delete', "id=$todo->id", '', 'list', '', 'hiddenwin'); + ?> +
      +
      + createLink('todo', 'import2Today'); + echo html::commonButton($lang->todo->import2Today, "onclick=\"changeAction('todoform', 'import2Today', '$actionLink')\""); + } + ?> +
      + show();?> +
      +
      + diff --git a/trunk/module/product/config.php b/trunk/module/product/config.php new file mode 100644 index 0000000000..bb0d8a804e --- /dev/null +++ b/trunk/module/product/config.php @@ -0,0 +1,74 @@ +product->orderBy = 'isClosed, `order`'; + +global $lang, $app; +$app->loadLang('story'); +$config->product->search['module'] = 'story'; +$config->product->search['fields']['title'] = $lang->story->title; +$config->product->search['fields']['id'] = $lang->story->id; +$config->product->search['fields']['keywords'] = $lang->story->keywords; +$config->product->search['fields']['stage'] = $lang->story->stage; +$config->product->search['fields']['status'] = $lang->story->status; +$config->product->search['fields']['pri'] = $lang->story->pri; + +$config->product->search['fields']['product'] = $lang->story->product; +$config->product->search['fields']['module'] = $lang->story->module; +$config->product->search['fields']['plan'] = $lang->story->plan; +$config->product->search['fields']['estimate'] = $lang->story->estimate; + +$config->product->search['fields']['source'] = $lang->story->source; +$config->product->search['fields']['fromBug'] = $lang->story->fromBug; + +$config->product->search['fields']['openedBy'] = $lang->story->openedBy; +$config->product->search['fields']['reviewedBy'] = $lang->story->reviewedBy; +$config->product->search['fields']['assignedTo'] = $lang->story->assignedTo; +$config->product->search['fields']['closedBy'] = $lang->story->closedBy; +$config->product->search['fields']['lastEditedBy'] = $lang->story->lastEditedBy; + +$config->product->search['fields']['mailto'] = $lang->story->mailto; + +$config->product->search['fields']['closedReason'] = $lang->story->closedReason; +$config->product->search['fields']['version'] = $lang->story->version; + +$config->product->search['fields']['openedDate'] = $lang->story->openedDate; +$config->product->search['fields']['reviewedDate'] = $lang->story->reviewedDate; +$config->product->search['fields']['assignedDate'] = $lang->story->assignedDate; +$config->product->search['fields']['closedDate'] = $lang->story->closedDate; +$config->product->search['fields']['lastEditedDate'] = $lang->story->lastEditedDate; + +$config->product->search['params']['title'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->product->search['params']['keywords'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->product->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->story->statusList); +$config->product->search['params']['stage'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->story->stageList); +$config->product->search['params']['pri'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->story->priList); + +$config->product->search['params']['product'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->product->search['params']['module'] = array('operator' => 'belong', 'control' => 'select', 'values' => ''); +$config->product->search['params']['plan'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->product->search['params']['estimate'] = array('operator' => '=', 'control' => 'input', 'values' => ''); + +$config->product->search['params']['source'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->story->sourceList); +$config->product->search['params']['fromBug'] = array('operator' => '=', 'control' => 'input', 'values' => ''); + +$config->product->search['params']['openedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->product->search['params']['reviewedBy'] = array('operator' => 'include', 'control' => 'select', 'values' => 'users'); +$config->product->search['params']['assignedTo'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->product->search['params']['closedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->product->search['params']['lastEditedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); + +$config->product->search['params']['mailto'] = array('operator' => 'include', 'control' => 'select', 'values' => 'users'); + +$config->product->search['params']['closedReason'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->story->reasonList); +$config->product->search['params']['version'] = array('operator' => '>=', 'control' => 'input', 'values' => ''); + +$config->product->search['params']['openedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->product->search['params']['reviewedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->product->search['params']['assignedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->product->search['params']['closedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->product->search['params']['lastEditedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); + +$config->product->create->requiredFields = 'name,code'; +$config->product->edit->requiredFields = 'name,code'; + +$config->product->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->product->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/trunk/module/product/control.php b/trunk/module/product/control.php new file mode 100644 index 0000000000..66c7729387 --- /dev/null +++ b/trunk/module/product/control.php @@ -0,0 +1,402 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +class product extends control +{ + public $products = array(); + + /** + * Construct function. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + + /* Load need modules. */ + $this->loadModel('story'); + $this->loadModel('release'); + $this->loadModel('tree'); + $this->loadModel('user'); + + /* Get all products, if no, goto the create page. */ + $this->products = $this->product->getPairs(); + if(empty($this->products) and strpos('create', $this->methodName) === false) $this->locate($this->createLink('product', 'create')); + $this->view->products = $this->products; + } + + /** + * Index page, to browse. + * + * @access public + * @return void + */ + public function index($locate = 'yes') + { + if($locate == 'yes') $this->locate($this->createLink($this->moduleName, 'browse')); + + $this->app->loadLang('my'); + $this->view->productStats = $this->product->getStats(); + $this->display(); + } + + /** + * project + * + * @param string $status + * @param int $productID + * @access public + * @return void + */ + public function project($status = 'all', $productID = 0) + { + $this->app->loadLang('my'); + $this->view->projectStats = $this->loadModel('project')->getProjectStats($status, $productID); + + $this->view->productID = $productID; + $this->display(); + } + + /** + * Browse a product. + * + * @param int $productID + * @param string $browseType + * @param int $param + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browse($productID = 0, $browseType = 'byModule', $param = 0, $orderBy = '', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Lower browse type. */ + $browseType = strtolower($browseType); + + /* Save session. */ + $this->session->set('storyList', $this->app->getURI(true)); + $this->session->set('productList', $this->app->getURI(true)); + + /* Set product, module and query. */ + $productID = $this->product->saveState($productID, $this->products); + $moduleID = ($browseType == 'bymodule') ? (int)$param : 0; + $queryID = ($browseType == 'bysearch') ? (int)$param : 0; + + /* Set menu. */ + $this->product->setMenu($this->products, $productID); + + /* Process the order by field. */ + if(!$orderBy) $orderBy = $this->cookie->productStoryOrder ? $this->cookie->productStoryOrder : 'id_desc'; + setcookie('productStoryOrder', $orderBy, $this->config->cookieLife, $this->config->webRoot); + + /* Set header and position. */ + $this->view->header->title = $this->products[$productID]. $this->lang->colon . $this->lang->product->browse; + $this->view->position[] = $this->products[$productID]; + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + /* Get stories. */ + $stories = array(); + if($browseType == 'allstory') $stories = $this->story->getProductStories($productID, 0, 'all', $orderBy, $pager); + if($browseType == 'bymodule') $stories = $this->story->getProductStories($productID, $this->tree->getAllChildID($moduleID), 'all', $orderBy, $pager); + if($browseType == 'bysearch') $stories = $this->story->getBySearch($productID, $queryID, $orderBy, $pager); + if($browseType == 'assignedtome')$stories = $this->story->getByAssignedTo($productID, $this->app->user->account, $orderBy, $pager); + if($browseType == 'openedbyme') $stories = $this->story->getByOpenedBy($productID, $this->app->user->account, $orderBy, $pager); + if($browseType == 'reviewedbyme')$stories = $this->story->getByReviewedBy($productID, $this->app->user->account, $orderBy, $pager); + if($browseType == 'closedbyme') $stories = $this->story->getByClosedBy($productID, $this->app->user->account, $orderBy, $pager); + if($browseType == 'draftstory') $stories = $this->story->getByStatus($productID, 'draft', $orderBy, $pager); + if($browseType == 'activestory') $stories = $this->story->getByStatus($productID, 'active', $orderBy, $pager); + if($browseType == 'changedstory')$stories = $this->story->getByStatus($productID, 'changed', $orderBy, $pager); + if($browseType == 'closedstory') $stories = $this->story->getByStatus($productID, 'closed', $orderBy, $pager); + + /* Process the sql, get the conditon partion, save it to session. */ + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story'); + + /* Build search form. */ + $this->config->product->search['actionURL'] = $this->createLink('product', 'browse', "productID=$productID&browseType=bySearch&queryID=myQueryID"); + $this->config->product->search['queryID'] = $queryID; + $this->config->product->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productID); + $this->config->product->search['params']['product']['values'] = array($productID => $this->products[$productID], 'all' => $this->lang->product->allProduct); + $this->config->product->search['params']['module']['values'] = $this->tree->getOptionMenu($productID, $viewType = 'story', $startModuleID = 0); + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->product->search); + + $this->view->productID = $productID; + $this->view->productName = $this->products[$productID]; + $this->view->moduleID = $moduleID; + $this->view->stories = $stories; + $this->view->moduleTree = $this->tree->getTreeMenu($productID, $viewType = 'story', $startModuleID = 0, array('treeModel', 'createStoryLink')); + $this->view->parentModules = $this->tree->getParents($moduleID); + $this->view->pager = $pager; + $this->view->users = $this->user->getPairs('noletter'); + $this->view->orderBy = $orderBy; + $this->view->browseType = $browseType; + $this->view->moduleID = $moduleID; + $this->view->treeClass = $browseType == 'bymodule' ? '' : 'hidden'; + $this->display(); + } + + /** + * Create a product. + * + * @access public + * @return void + */ + public function create() + { + if(!empty($_POST)) + { + $productID = $this->product->create(); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action')->create('product', $productID, 'opened'); + die(js::locate($this->createLink($this->moduleName, 'browse', "productID=$productID"), 'parent')); + } + + $this->product->setMenu($this->products, key($this->products)); + + $this->view->header->title = $this->lang->product->create; + $this->view->position[] = $this->view->header->title; + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->users = $this->loadModel('user')->getPairs(); + $this->display(); + } + + /** + * Edit a product. + * + * @param int $productID + * @access public + * @return void + */ + public function edit($productID) + { + if(!empty($_POST)) + { + $changes = $this->product->update($productID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('product', $productID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate(inlink('view', "product=$productID"), 'parent')); + } + + $this->product->setMenu($this->products, $productID); + + $product = $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch(); + $this->view->header->title = $this->lang->product->edit . $this->lang->colon . $product->name; + $this->view->position[] = html::a($this->createLink($this->moduleName, 'browse'), $product->name); + $this->view->position[] = $this->lang->product->edit; + $this->view->product = $product; + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->users = $this->loadModel('user')->getPairs(); + + $this->display(); + } + + /** + * View a product. + * + * @param int $productID + * @access public + * @return void + */ + public function view($productID) + { + $this->product->setMenu($this->products, $productID); + + $product = $this->product->getStatByID($productID); + $product->desc = $this->loadModel('file')->setImgSize($product->desc); + if(!$product) die(js::error($this->lang->notFound) . js::locate('back')); + + $this->view->header->title = $product->name . ' - ' . $this->lang->product->view; + $this->view->position[] = html::a($this->createLink($this->moduleName, 'browse'), $product->name); + $this->view->position[] = $this->lang->product->view; + $this->view->product = $product; + $this->view->actions = $this->loadModel('action')->getList('product', $productID); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->groups = $this->loadModel('group')->getPairs(); + + $this->display(); + } + + /** + * Delete a product. + * + * @param int $productID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($productID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->product->confirmDelete, $this->createLink('product', 'delete', "productID=$productID&confirm=yes"))); + } + else + { + $this->product->delete(TABLE_PRODUCT, $productID); + $this->session->set('product', ''); // 清除session。 + die(js::locate($this->createLink('product', 'browse'), 'parent')); + } + } + + /** + * Docs of a product. + * + * @param int $productID + * @access public + * @return void + */ + public function doc($productID) + { + $this->product->setMenu($this->products, $productID); + $this->session->set('docList', $this->app->getURI(true)); + + $product = $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch(); + $this->view->header->title = $product->name . $this->lang->colon . $this->lang->product->doc; + $this->view->position[] = html::a($this->createLink($this->moduleName, 'browse'), $product->name); + $this->view->position[] = $this->lang->product->doc; + $this->view->product = $product; + $this->view->docs = $this->loadModel('doc')->getProductDocs($productID); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->display(); + } + + /** + * Road map of a product. + * + * @param int $productID + * @access public + * @return void + */ + public function roadmap($productID) + { + $this->product->setMenu($this->products, $productID); + + $this->session->set('releaseList', $this->app->getURI(true)); + $this->session->set('productPlanList', $this->app->getURI(true)); + + $product = $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch(); + $this->view->header->title = $product->name . $this->lang->colon . $this->lang->product->roadmap; + $this->view->position[] = html::a($this->createLink($this->moduleName, 'browse'), $product->name); + $this->view->position[] = $this->lang->product->roadmap; + $this->view->product = $product; + $this->view->roadmaps = $this->product->getRoadmap($productID); + + $this->display(); + } + + /** + * Product dynamic. + * + * @param string $type + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function dynamic($productID = 0, $type = 'today', $param = '', $orderBy = 'date_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $uri = $this->app->getURI(true); + $this->session->set('productList', $uri); + $this->session->set('productPlanList', $uri); + $this->session->set('releaseList', $uri); + $this->session->set('storyList', $uri); + $this->session->set('projectList', $uri); + $this->session->set('taskList', $uri); + $this->session->set('buildList', $uri); + $this->session->set('bugList', $uri); + $this->session->set('caseList', $uri); + $this->session->set('testtaskList', $uri); + + $this->product->setMenu($this->products, $productID); + + /* Set the pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; + + /* Set the user and type. */ + $account = $type == 'account' ? $param : 'all'; + $period = $type == 'account' ? 'all' : $type; + + /* The header and position. */ + $this->view->header->title = $this->products[$productID] . $this->lang->colon . $this->lang->product->dynamic; + $this->view->position[] = $this->lang->product->dynamic; + + /* Assign. */ + $this->view->productID = $productID; + $this->view->type = $type; + $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noletter'); + $this->view->account = $account; + $this->view->actions = $this->loadModel('action')->getDynamic($account, $period, $orderBy, $pager, $productID); + $this->display(); + } + + /** + * order product + * + * @param int $productID + * @access public + * @return void + */ + public function order($productID) + { + if($_POST) + { + $this->product->saveOrder(); + die(js::reload('parent')); + } + $this->product->setMenu($this->products, $productID); + $this->view->products = $this->product->getList('noclosed'); + $this->display(); + } + + /** + * AJAX: get projects of a product in html select. + * + * @param int $productID + * @param int $projectID + * @access public + * @return void + */ + public function ajaxGetProjects($productID, $projectID = 0) + { + $projects = $this->product->getProjectPairs($productID, $params = 'nodeleted'); + die(html::select('project', $projects, $projectID, 'class=select-3 onchange=loadProjectRelated(this.value)')); + } + + /** + * AJAX: get plans of a product in html select. + * + * @param int $productID + * @param int $planID + * @access public + * @return void + */ + public function ajaxGetPlans($productID, $planID = 0) + { + $plans = $this->loadModel('productplan')->getPairs($productID); + die(html::select('plan', $plans, $planID)); + } +} diff --git a/trunk/module/product/js/browse.js b/trunk/module/product/js/browse.js new file mode 100644 index 0000000000..6135a247be --- /dev/null +++ b/trunk/module/product/js/browse.js @@ -0,0 +1,23 @@ +/* Browse by module. */ +function browseByModule() +{ + $('#treebox').removeClass('hidden'); + $('.divider').removeClass('hidden'); + $('#querybox').addClass('hidden'); + $('#featurebar .active').removeClass('active'); + $('#bymoduleTab').addClass('active'); +} + +/** + * Change form action. + * + * @param formName $formName + * @param actionName $actionName + * @param actionLink $actionLink + * @access public + * @return void + */ +function changeAction(formName, actionName, actionLink) +{ + $('#' + formName).attr('action', actionLink).submit(); +} diff --git a/trunk/module/product/js/common.js b/trunk/module/product/js/common.js new file mode 100644 index 0000000000..49b8e6e3a4 --- /dev/null +++ b/trunk/module/product/js/common.js @@ -0,0 +1,4 @@ +function setWhite(acl) +{ + acl == 'custom' ? $('#whitelistBox').removeClass('hidden') : $('#whitelistBox').addClass('hidden'); +} diff --git a/trunk/module/product/js/dynamic.js b/trunk/module/product/js/dynamic.js new file mode 100755 index 0000000000..094de9d376 --- /dev/null +++ b/trunk/module/product/js/dynamic.js @@ -0,0 +1,5 @@ +function changeUser(account, productID) +{ + link = createLink('product', 'dynamic', 'productID=' + productID + '&type=account¶m=' + account); + location.href = link; +} diff --git a/trunk/module/product/js/index.js b/trunk/module/product/js/index.js new file mode 100644 index 0000000000..742a3b7329 --- /dev/null +++ b/trunk/module/product/js/index.js @@ -0,0 +1,7 @@ +$().ready(function() +{ + $('.projectline').each(function() + { + $(this).sparkline('html', {height:'25px'}); + }) +}) diff --git a/trunk/module/product/js/project.js b/trunk/module/product/js/project.js new file mode 100644 index 0000000000..742a3b7329 --- /dev/null +++ b/trunk/module/product/js/project.js @@ -0,0 +1,7 @@ +$().ready(function() +{ + $('.projectline').each(function() + { + $(this).sparkline('html', {height:'25px'}); + }) +}) diff --git a/trunk/module/product/lang/en.php b/trunk/module/product/lang/en.php new file mode 100644 index 0000000000..71731a2d3a --- /dev/null +++ b/trunk/module/product/lang/en.php @@ -0,0 +1,85 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->product->common = 'Product'; +$lang->product->index = "Index"; +$lang->product->browse = "Browse"; +$lang->product->dynamic= "Dynamic"; +$lang->product->view = "Info"; +$lang->product->edit = "Edit"; +$lang->product->create = "Create"; +$lang->product->read = "Info"; +$lang->product->delete = "Delete"; +$lang->product->select = '--select product--'; + +$lang->product->basicInfo = 'Basic info'; +$lang->product->otherInfo = 'Other info'; + +$lang->product->plans = 'Plans'; +$lang->product->releases = 'Releases'; +$lang->product->docs = 'Documents'; +$lang->product->bugs = 'Bugs'; +$lang->product->projects = 'Projects'; +$lang->product->cases = 'Cases'; +$lang->product->bulids = 'Bulids'; +$lang->product->roadmap = 'Roadmap'; +$lang->product->doc = 'Doc'; +$lang->product->project = 'Projects'; + +$lang->product->selectProduct = "Select product"; +$lang->product->saveButton = " Save (S) "; +$lang->product->confirmDelete = " Are you sure to delete this product?"; +$lang->product->ajaxGetProjects = "API: projects of product"; +$lang->product->ajaxGetPlans = "API: plans of product"; + +$lang->product->errorFormat = 'Error format.'; +$lang->product->errorEmptyName = 'Name can not be empty.'; +$lang->product->errorEmptyCode = 'Code can not be empty'; +$lang->product->errorNoProduct = 'No product in system yet.'; +$lang->product->accessDenied = 'Access to this product denined.'; + +$lang->product->id = 'ID'; +$lang->product->company = 'Company'; +$lang->product->name = 'Name'; +$lang->product->code = 'Code'; +$lang->product->order = 'Order'; +$lang->product->status = 'Status'; +$lang->product->desc = 'Desc'; +$lang->product->PO = 'Product owner'; +$lang->product->QM = 'Test manager'; +$lang->product->RM = 'Release manager'; +$lang->product->acl = 'Access limitation'; +$lang->product->whitelist = 'Whitelist'; + +$lang->product->moduleStory = 'By module'; +$lang->product->searchStory = 'Search'; +$lang->product->assignedToMe = 'To me'; +$lang->product->openedByMe = 'Opened by me'; +$lang->product->reviewedByMe = 'Reviewed by me'; +$lang->product->closedByMe = 'Closed by me'; +$lang->product->draftStory = 'Draft'; +$lang->product->activeStory = 'Active'; +$lang->product->changedStory = 'Changed'; +$lang->product->closedStory = 'Closed'; + +$lang->product->allStory = 'All'; +$lang->product->allProduct = 'All products'; +$lang->product->allProductsOfProject = 'All related products'; + +$lang->product->statusList[''] = ''; +$lang->product->statusList['normal'] = 'Normal'; +$lang->product->statusList['closed'] = 'Closed'; + +$lang->product->aclList['open'] = 'Default(Having product module prividge, can visit this product)'; +$lang->product->aclList['private'] = 'Private(Only project team members can visit)'; +$lang->product->aclList['custom'] = 'Whitelist(Project team members and who belongs to the whilelist groups can visit)'; + +$lang->product->storySummary = "Total %s stories in this page, estimate %s hours."; diff --git a/trunk/module/product/lang/zh-cn.php b/trunk/module/product/lang/zh-cn.php new file mode 100644 index 0000000000..efa47a87d2 --- /dev/null +++ b/trunk/module/product/lang/zh-cn.php @@ -0,0 +1,85 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->product->common = '产品视图'; +$lang->product->index = "产品首页"; +$lang->product->browse = "浏览产品"; +$lang->product->dynamic= "动态"; +$lang->product->view = "产品信息"; +$lang->product->edit = "编辑产品"; +$lang->product->create = "新增产品"; +$lang->product->read = "产品详情"; +$lang->product->delete = "删除产品"; +$lang->product->select = '--请选择产品--'; + +$lang->product->basicInfo = '基本信息'; +$lang->product->otherInfo = '其他信息'; + +$lang->product->plans = '计划数'; +$lang->product->releases = '发布数'; +$lang->product->docs = '文档数'; +$lang->product->bugs = '相关BUG'; +$lang->product->projects = '关联项目数'; +$lang->product->cases = '用例数'; +$lang->product->bulids = 'BULID数'; +$lang->product->roadmap = '路线图'; +$lang->product->doc = '文档列表'; +$lang->product->project = '项目列表'; + +$lang->product->selectProduct = "请选择产品"; +$lang->product->saveButton = " 保存 (S) "; +$lang->product->confirmDelete = " 您确定删除该产品吗?"; +$lang->product->ajaxGetProjects = "接口:项目列表"; +$lang->product->ajaxGetPlans = "接口:计划列表"; + +$lang->product->errorFormat = '产品数据格式不正确'; +$lang->product->errorEmptyName = '产品名称不能为空'; +$lang->product->errorEmptyCode = '产品代号不能为空'; +$lang->product->errorNoProduct = '还没有创建产品!'; +$lang->product->accessDenied = '您无权访问该产品'; + +$lang->product->id = '编号'; +$lang->product->company = '所属公司'; +$lang->product->name = '产品名称'; +$lang->product->code = '产品代号'; +$lang->product->order = '排序'; +$lang->product->status = '状态'; +$lang->product->desc = '产品描述'; +$lang->product->PO = '产品负责人'; +$lang->product->QM = '测试负责人'; +$lang->product->RM = '发布负责人'; +$lang->product->acl = '访问控制'; +$lang->product->whitelist = '分组白名单'; + +$lang->product->moduleStory = '按模块'; +$lang->product->searchStory = '搜索'; +$lang->product->assignedToMe = '指派给我'; +$lang->product->openedByMe = '由我创建'; +$lang->product->reviewedByMe = '由我评审'; +$lang->product->closedByMe = '由我关闭'; +$lang->product->draftStory = '草稿'; +$lang->product->activeStory = '激活'; +$lang->product->changedStory = '已变更'; +$lang->product->closedStory = '已关闭'; + +$lang->product->allStory = '全部需求'; +$lang->product->allProduct = '全部产品'; +$lang->product->allProductsOfProject = '全部关联产品'; + +$lang->product->statusList[''] = ''; +$lang->product->statusList['normal'] = '正常'; +$lang->product->statusList['closed'] = '结束'; + +$lang->product->aclList['open'] = '默认设置(有产品视图权限,即可访问)'; +$lang->product->aclList['private'] = '私有项目(只有项目团队成员才能访问)'; +$lang->product->aclList['custom'] = '自定义白名单(团队成员和白名单的成员可以访问)'; + +$lang->product->storySummary = "本页共 %s 个需求,预计 %s 个工时。"; diff --git a/trunk/module/product/lang/zh-tw.php b/trunk/module/product/lang/zh-tw.php new file mode 100644 index 0000000000..a800657011 --- /dev/null +++ b/trunk/module/product/lang/zh-tw.php @@ -0,0 +1,85 @@ + + * @package product + * @version $Id: zh-tw.php 3343 2012-07-16 01:15:26Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +$lang->product->common = '產品視圖'; +$lang->product->index = "產品首頁"; +$lang->product->browse = "瀏覽產品"; +$lang->product->dynamic= "動態"; +$lang->product->view = "產品信息"; +$lang->product->edit = "編輯產品"; +$lang->product->create = "新增產品"; +$lang->product->read = "產品詳情"; +$lang->product->delete = "刪除產品"; +$lang->product->select = '--請選擇產品--'; + +$lang->product->basicInfo = '基本信息'; +$lang->product->otherInfo = '其他信息'; + +$lang->product->plans = '計劃數'; +$lang->product->releases = '發佈數'; +$lang->product->docs = '文檔數'; +$lang->product->bugs = '相關BUG'; +$lang->product->projects = '關聯項目數'; +$lang->product->cases = '用例數'; +$lang->product->bulids = 'BULID數'; +$lang->product->roadmap = '路線圖'; +$lang->product->doc = '文檔列表'; +$lang->product->project = '項目列表'; + +$lang->product->selectProduct = "請選擇產品"; +$lang->product->saveButton = " 保存 (S) "; +$lang->product->confirmDelete = " 您確定刪除該產品嗎?"; +$lang->product->ajaxGetProjects = "介面:項目列表"; +$lang->product->ajaxGetPlans = "介面:計劃列表"; + +$lang->product->errorFormat = '產品數據格式不正確'; +$lang->product->errorEmptyName = '產品名稱不能為空'; +$lang->product->errorEmptyCode = '產品代號不能為空'; +$lang->product->errorNoProduct = '還沒有創建產品!'; +$lang->product->accessDenied = '您無權訪問該產品'; + +$lang->product->id = '編號'; +$lang->product->company = '所屬公司'; +$lang->product->name = '產品名稱'; +$lang->product->code = '產品代號'; +$lang->product->order = '排序'; +$lang->product->status = '狀態'; +$lang->product->desc = '產品描述'; +$lang->product->PO = '產品負責人'; +$lang->product->QM = '測試負責人'; +$lang->product->RM = '發佈負責人'; +$lang->product->acl = '訪問控制'; +$lang->product->whitelist = '分組白名單'; + +$lang->product->moduleStory = '按模組'; +$lang->product->searchStory = '搜索'; +$lang->product->assignedToMe = '指派給我'; +$lang->product->openedByMe = '由我創建'; +$lang->product->reviewedByMe = '由我評審'; +$lang->product->closedByMe = '由我關閉'; +$lang->product->draftStory = '草稿'; +$lang->product->activeStory = '激活'; +$lang->product->changedStory = '已變更'; +$lang->product->closedStory = '已關閉'; + +$lang->product->allStory = '全部需求'; +$lang->product->allProduct = '全部產品'; +$lang->product->allProductsOfProject = '全部關聯產品'; + +$lang->product->statusList[''] = ''; +$lang->product->statusList['normal'] = '正常'; +$lang->product->statusList['closed'] = '結束'; + +$lang->product->aclList['open'] = '預設設置(有產品視圖權限,即可訪問)'; +$lang->product->aclList['private'] = '私有項目(只有項目團隊成員才能訪問)'; +$lang->product->aclList['custom'] = '自定義白名單(團隊成員和白名單的成員可以訪問)'; + +$lang->product->storySummary = "本頁共 %s 個需求,預計 %s 個工時。"; diff --git a/trunk/module/product/model.php b/trunk/module/product/model.php new file mode 100644 index 0000000000..994550cabe --- /dev/null +++ b/trunk/module/product/model.php @@ -0,0 +1,531 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> +checkPriv($this->getById($productID))) + { + echo(js::alert($this->lang->product->accessDenied)); + die(js::locate('back')); + } + + $currentModule = $this->app->getModuleName(); + $currentMethod = $this->app->getMethodName(); + + /* init currentModule and currentMethod for report*/ + if($currentModule == 'story') $currentModule = 'product'; + if($currentMethod == 'report') $currentMethod = 'browse'; + + $selectHtml = $this->select($products, $productID, $currentModule, $currentMethod, $extra); + foreach($this->lang->product->menu as $key => $menu) + { + $replace = $key == 'list' ? $selectHtml : $productID; + common::setMenuVars($this->lang->product->menu, $key, $replace); + } + } + + /** + * Create the select code of products. + * + * @param array $products + * @param int $productID + * @param string $currentModule + * @param string $currentMethod + * @param string $extra + * @access public + * @return string + */ + public function select($products, $productID, $currentModule, $currentMethod, $extra = '') + { + $productMode = $this->cookie->productMode ? $this->cookie->productMode : 'all'; + $productGroup = array(); + $products = $this->dao->select('id, status, name')->from(TABLE_PRODUCT)->where('id')->in(array_keys($products))->orderBy('`order`')->fetchAll(); + foreach($products as $product) + { + if($productMode == 'noclosed' and $product->status == 'closed') continue; + if($product->status != 'closed') + { + $productGroup[' '][$product->id] = $product->name; + } + elseif($product->status == 'closed') + { + $productGroup[$this->lang->product->statusList['closed']][$product->id] = $product->name; + } + } + + /** + * 1. if user selected by mouse, reload it. + * 2. if the user select by keyboard, save the event.keyCode, thus the switchProduct() can judge whether reload or not. + * 3. if user press enter key in the select, reload it. + * 4. if user click the go button, reload it. + * */ + $switchCode = "switchProduct($('#productID').val(), '$currentModule', '$currentMethod', '$extra');"; + $onchange = "onchange=\"$switchCode\""; + $selectHtml = html::selectGroup('productID', $productGroup, $productID, "tabindex=2 $onchange"); + + return $selectHtml; + } + + /** + * Save the product id user last visited to session. + * + * @param int $productID + * @param array $products + * @access public + * @return int + */ + public function saveState($productID, $products) + { + if($productID > 0) $this->session->set('product', (int)$productID); + if($productID == 0 and $this->cookie->lastProduct) $this->session->set('product', (int)$this->cookie->lastProduct); + if($productID == 0 and $this->session->product == '') $this->session->set('product', key($products)); + if(!isset($products[$this->session->product])) $this->session->set('product', key($products)); + return $this->session->product; + } + + /** + * Save order + * + * @access public + * @return void + */ + public function saveOrder() + { + foreach($_POST as $productID => $order) + { + $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($order)->where('id')->eq($productID)->exec(); + } + } + + /** + * Check privilege. + * + * @param int $product + * @access public + * @return bool + */ + public function checkPriv($product) + { + /* Is admin? */ + $account = ',' . $this->app->user->account . ','; + if(strpos($this->app->company->admins, $account) !== false) return true; + + /* Product is open, return true. */ + if($product->acl == 'open') return true; + + /* Get team members. */ + $teamMembers = $this->getTeamMemberPairs($product); + + /* Private. */ + if($product->acl == 'private') + { + return isset($teamMembers[$this->app->user->account]); + } + + /* Custom, check groups. */ + if($product->acl == 'custom') + { + if(isset($teamMembers[$this->app->user->account])) return true; + $userGroups = $this->app->user->groups; + $productGroups = explode(',', $product->whitelist); + foreach($userGroups as $groupID) + { + if(in_array($groupID, $productGroups)) return true; + } + return false; + } + } + + /** + * Get product by id. + * + * @param int $productID + * @access public + * @return object + */ + public function getById($productID) + { + return $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch(); + } + + /** + * Get products. + * + * @param string $status + * @param int $limit + * @access public + * @return array + */ + public function getList($status = 'all', $limit = 0) + { + return $this->dao->select('*')->from(TABLE_PRODUCT) + ->where('deleted')->eq(0) + ->beginIF($status = 'noclosed')->andWhere('status')->ne('closed')->fi() + ->beginIF($status != 'all' and $status != 'noclosed')->andWhere('status')->in($status)->fi() + ->beginIF($limit > 0)->limit($limit)->fi() + ->orderBy('`order` asc') + ->fetchAll('id'); + } + + /** + * Get product pairs. + * + * @param string $mode + * @return array + */ + public function getPairs($mode = '') + { + $orderBy = !empty($this->config->product->orderBy) ? $this->config->product->orderBy : 'isClosed, `order`'; + $mode .= $this->cookie->productMode; + $products = $this->dao->select('*, IF(INSTR(" closed", status) < 2, 0, 1) AS isClosed') + ->from(TABLE_PRODUCT) + ->where('deleted')->eq(0) + ->beginIF(strpos($mode, 'noclosed') !== false)->andWhere('status')->ne('closed')->fi() + ->orderBy($orderBy) + ->fetchAll(); + $pairs = array(); + foreach($products as $product) + { + if($this->checkPriv($product)) + { + + if(strpos($mode, 'nocode') === false and $product->code) + { + $firstChar = strtoupper(substr($product->code, 0, 1)); + if(ord($firstChar) < 127) $product->name = $firstChar . ':' . $product->name; + } + + $pairs[$product->id] = $product->name; + } + } + return $pairs; + } + + /** + * Get products by project. + * + * @param int $projectID + * @access public + * @return array + */ + public function getProductsByProject($projectID) + { + return $this->dao->select('t1.product, t2.name') + ->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t1.project')->eq($projectID) + ->fetchPairs(); + } + + /** + * Get grouped products. + * + * @access public + * @return void + */ + public function getStatusGroups() + { + $products = $this->dao->select('id, name, status')->from(TABLE_PRODUCT)->where('deleted')->eq(0)->fetchGroup('status'); + } + + /** + * Create a product. + * + * @access public + * @return int + */ + public function create() + { + $product = fixer::input('post') + ->stripTags('name,code') + ->setIF($this->post->acl != 'custom', 'whitelist', '') + ->setDefault('status', 'normal') + ->setDefault('createdBy', $this->app->user->account) + ->setDefault('createdDate', helper::now()) + ->join('whitelist', ',') + ->get(); + $this->dao->insert(TABLE_PRODUCT) + ->data($product) + ->autoCheck() + ->batchCheck('name,code', 'notempty') + ->check('name', 'unique') + ->check('code', 'unique') + ->exec(); + return $this->dao->lastInsertID(); + } + + /** + * Update a product. + * + * @param int $productID + * @access public + * @return array + */ + public function update($productID) + { + $productID = (int)$productID; + $oldProduct = $this->getById($productID); + $product = fixer::input('post') + ->stripTags('name,code') + ->setIF($this->post->acl != 'custom', 'whitelist', '') + ->join('whitelist', ',') + ->get(); + $this->dao->update(TABLE_PRODUCT) + ->data($product) + ->autoCheck() + ->batchCheck('name,code', 'notempty') + ->check('name', 'unique', "id != $productID") + ->check('code', 'unique', "id != $productID") + ->where('id')->eq($productID) + ->exec(); + if(!dao::isError()) return common::createChanges($oldProduct, $product); + } + + /** + * Get projects of a product in pairs. + * + * @param int $productID + * @param string $param all|nodeleted + * @access public + * @return array + */ + public function getProjectPairs($productID, $param = 'all') + { + $projects = array(); + $datas = $this->dao->select('t2.id, t2.name, t2.deleted') + ->from(TABLE_PROJECTPRODUCT)->alias('t1')->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.product')->eq((int)$productID) + ->orderBy('t1.project desc') + ->fetchAll(); + + foreach($datas as $data) + { + if($param == 'nodeleted' and $data->deleted) continue; + $projects[$data->id] = $data->name; + } + $projects = array('' => '') + $projects; + return $projects; + } + + /** + * Get roadmap of a proejct + * + * @param int $productID + * @access public + * @return array + */ + public function getRoadmap($productID) + { + $plans = $this->loadModel('productplan')->getList($productID); + $releases = $this->loadModel('release')->getList($productID); + $roadmap = array(); + if(is_array($releases)) $releases = array_reverse($releases); + foreach($releases as $release) + { + $year = substr($release->date, 0, 4); + $roadmap[$year][] = $release; + } + foreach($plans as $plan) + { + if($plan->end != '0000-00-00' and strtotime($plan->end) - time() <= 0) continue; + $year = substr($plan->end, 0, 4); + $roadmap[$year][] = $plan; + } + + ksort($roadmap); + return $roadmap; + } + + /** + * Get team members of a product from projects. + * + * @param object $product + * @access public + * @return array + */ + public function getTeamMemberPairs($product) + { + $members[$product->PO] = $product->PO; + $members[$product->QM] = $product->QM; + $members[$product->RM] = $product->RM; + $members[$product->createdBy] = $product->createdBy; + + /* Set projects and teams as static thus we can only query sql one times. */ + static $projects, $teams; + if(empty($projects)) $projects = $this->dao->select('project, product')->from(TABLE_PROJECTPRODUCT)->fetchGroup('product', 'project'); + if(empty($teams)) $teams = $this->dao->select('project, account')->from(TABLE_TEAM)->fetchGroup('project', 'account'); + + if(!isset($projects[$product->id])) return $members; + $productProjects = $projects[$product->id]; + + $projectTeams = array(); + foreach($teams as $projectID => $projectTeam) $projectTeams = $projectTeams + array_keys($projectTeam); + + return array_merge($members, $projectTeams); + } + + /** + * Get product stat by id + * + * @param int $productID + * @access public + * @return object|bool + */ + public function getStatByID($productID) + { + $product = $this->getById($productID); + if(!$this->checkPriv($product)) return false; + $stories = $this->dao->select('product, status, count(status) AS count')->from(TABLE_STORY)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->groupBy('product, status')->fetchAll('status'); + /* Padding the stories to sure all status have records. */ + foreach(array_keys($this->lang->story->statusList) as $status) + { + $stories[$status] = isset($stories[$status]) ? $stories[$status]->count : 0; + } + + $plans = $this->dao->select('count(*) AS count')->from(TABLE_PRODUCTPLAN)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->andWhere('end')->gt(helper::now())->fetch(); + $bulids = $this->dao->select('count(*) AS count')->from(TABLE_BUILD)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->fetch(); + $cases = $this->dao->select('count(*) AS count')->from(TABLE_CASE)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->fetch(); + $bugs = $this->dao->select('count(*) AS count')->from(TABLE_BUG)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->fetch(); + $docs = $this->dao->select('count(*) AS count')->from(TABLE_DOC)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->fetch(); + $releases = $this->dao->select('count(*) AS count')->from(TABLE_RELEASE)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->fetch(); + $projects = $this->dao->select('count("t1.*") AS count')->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t2.deleted')->eq(0) + ->andWhere('t1.product')->eq($productID) + ->fetch(); + + $product->stories = $stories; + $product->plans = $plans ? $plans->count : 0; + $product->releases = $releases ? $releases->count : 0; + $product->bulids = $bulids ? $bulids->count : 0; + $product->cases = $cases ? $cases->count : 0; + $product->projects = $projects ? $projects->count : 0; + $product->bugs = $bugs ? $bugs->count : 0; + $product->docs = $docs ? $docs->count : 0; + + return $product; + } + + /** + * Get product stats. + * + * @access public + * @return array + */ + public function getStats() + { + $this->loadModel('report'); + $this->loadModel('story'); + $this->loadModel('bug'); + + $products = $this->getList(',normal'); + $stats = array(); + + $stories = $this->dao->select('product, status, count(status) AS count') + ->from(TABLE_STORY) + ->where('deleted')->eq(0) + ->andWhere('product')->in(array_keys($products)) + ->groupBy('product, status') + ->fetchGroup('product', 'status'); + + /* Padding the stories to sure all products have records. */ + $emptyStory = array_keys($this->lang->story->statusList); + foreach(array_keys($products) as $productID) + { + if(!isset($stories[$productID])) $stories[$productID] = $emptyStory; + } + + /* Padding the stories to sure all status have records. */ + foreach($stories as $key => $story) + { + foreach(array_keys($this->lang->story->statusList) as $status) + { + $story[$status] = isset($story[$status]) ? $story[$status]->count : 0; + } + $stories[$key] = $story; + } + + $plans = $this->dao->select('product, count(*) AS count') + ->from(TABLE_PRODUCTPLAN) + ->where('deleted')->eq(0) + ->andWhere('product')->in(array_keys($products)) + ->andWhere('end')->gt(helper::now()) + ->groupBy('product') + ->fetchPairs(); + + $releases = $this->dao->select('product, count(*) AS count') + ->from(TABLE_RELEASE) + ->where('deleted')->eq(0) + ->andWhere('product')->in(array_keys($products)) + ->groupBy('product') + ->fetchPairs(); + + $bugs = $this->dao->select('product,count(*) AS conut') + ->from(TABLE_BUG) + ->where('deleted')->eq(0) + ->andWhere('product')->in(array_keys($products)) + ->groupBy('product') + ->fetchPairs(); + $unResolved = $this->dao->select('product,count(*) AS count') + ->from(TABLE_BUG) + ->where('status')->eq('active') + ->andwhere('deleted')->eq(0) + ->andWhere('product')->in(array_keys($products)) + ->groupBy('product') + ->fetchPairs(); + $assignToNull = $this->dao->select('product,count(*) AS count') + ->from(TABLE_BUG) + ->where('AssignedTo')->eq('') + ->andwhere('deleted')->eq(0) + ->andWhere('product')->in(array_keys($products)) + ->groupBy('product') + ->fetchPairs(); + foreach($products as $key => $product) + { + if($this->checkPriv($product)) + { + if($product->status != 'closed') + { + $product->stories = $stories[$product->id]; + $product->plans = isset($plans[$product->id]) ? $plans[$product->id] : 0; + $product->releases= isset($releases[$product->id]) ? $releases[$product->id] : 0; + + $product->bugs = isset($bugs[$product->id]) ? $bugs[$product->id] : 0; + $product->unResolved = isset($unResolved[$product->id]) ? $unResolved[$product->id] : 0; + $product->assignToNull = isset($assignToNull[$product->id]) ? $assignToNull[$product->id] : 0; + $stats[] = $product; + } + } + else + { + unset($products[$key]); + } + } + + return $stats; + } +} diff --git a/trunk/module/product/view/browse.html.php b/trunk/module/product/view/browse.html.php new file mode 100644 index 0000000000..b23cde9e8d --- /dev/null +++ b/trunk/module/product/view/browse.html.php @@ -0,0 +1,138 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + +
      +
      + inlink('browse',"productID=$productID"), $lang->product->moduleStory);?> + inlink('browse', "productID=$productID&browseType=allStory"), $lang->product->allStory);?> + inlink('browse', "productID=$productID&browseType=assignedtome"), $lang->product->assignedToMe);?> + inlink('browse', "productID=$productID&browseType=openedByMe"), $lang->product->openedByMe);?> + inlink('browse', "productID=$productID&browseType=reviewedByMe"), $lang->product->reviewedByMe);?> + inlink('browse', "productID=$productID&browseType=closedByMe"), $lang->product->closedByMe);?> + inlink('browse', "productID=$productID&browseType=draftStory"), $lang->product->draftStory);?> + inlink('browse', "productID=$productID&browseType=activeStory"), $lang->product->activeStory);?> + inlink('browse', "productID=$productID&browseType=changedStory"), $lang->product->changedStory);?> + inlink('browse', "productID=$productID&browseType=closedStory"), $lang->product->closedStory);?> + product->searchStory;?> +
      +
      + + + + +
      +
      +
      '>
      +
      + + + + + + +
      +
      +
      + +
      + tree->manage);?> + tree->fix, 'hiddenwin');?> +
      +
      +
      + + + + recTotal}&recPerPage={$pager->recPerPage}";?> + + + + + + + + + + + + + + + + $story):?> + createLink('story', 'view', "storyID=$story->id"); + $totalEstimate += $story->estimate; + $canView = common::hasPriv('story', 'view'); + ?> + + + + + + + + + + + + + + + + + + + + +
      idAB);?>priAB);?>story->title);?>story->planAB);?>story->source);?>openedByAB);?>assignedToAB);?>story->estimateAB);?>statusAB);?>story->stageAB);?>actions;?>
      + + id)); else printf('%03d', $story->id);?> + story->priList[$story->pri];?>title);?>planTitle;?>story->sourceList[$story->source];?>openedBy];?>assignedTo];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?> + id}"; + common::printIcon('story', 'change', $vars, $story, 'list'); + common::printIcon('story', 'review', $vars, $story, 'list'); + common::printIcon('story', 'edit', "storyID=$story->id", $story, 'list'); + common::printIcon('story', 'createCase', "productID=$story->product&module=0&from=¶m=0&$vars", $story, 'list', 'createCase'); + ?> +
      +
      + createLink('story', 'batchClose', "from=productBrowse&productID=$productID&projectID=0&orderBy=$orderBy"); + echo html::commonButton($lang->story->batchClose, "onclick=\"changeAction('productStoryForm', 'batchClose', '$actionLink')\""); + } + } + printf($lang->product->storySummary, count($stories), $totalEstimate); + ?> +
      + show();?> +
      +
      +
      + + diff --git a/trunk/module/product/view/create.html.php b/trunk/module/product/view/create.html.php new file mode 100644 index 0000000000..2eb17fbdba --- /dev/null +++ b/trunk/module/product/view/create.html.php @@ -0,0 +1,54 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      product->create;?>
      product->name;?>
      product->code;?>
      product->PO;?>app->user->account, "class='select-3'");?>
      product->QM;?>app->user->account, "class='select-3'");?>
      product->RM;?>app->user->account, "class='select-3'");?>
      product->desc;?>
      product->acl;?>product->aclList, 'open', "onclick='setWhite(this.value);'"));?>
      +
      + diff --git a/trunk/module/product/view/doc.html.php b/trunk/module/product/view/doc.html.php new file mode 100644 index 0000000000..053396a2cd --- /dev/null +++ b/trunk/module/product/view/doc.html.php @@ -0,0 +1,54 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + $doc):?> + createLink('doc', 'view', "docID=$doc->id"); + $canView = common::hasPriv('doc', 'view'); + ?> + + + + + + + + + + +
      id}&projectID=0&from=product", $lang->doc->create);?>
      idAB;?>doc->module;?>doc->title;?>doc->addedBy;?>doc->addedDate;?>actions;?>
      id)); else printf('%03d', $doc->id);?>module;?>title);?>addedBy];?>addedDate;?> + id}"; + if(!common::printLink('doc', 'edit', $vars, $lang->edit)) echo $lang->edit; + if(!common::printLink('doc', 'delete', $vars, $lang->delete, 'hiddenwin')) echo $lang->delete; + ?> +
      +
      + diff --git a/trunk/module/product/view/dynamic.html.php b/trunk/module/product/view/dynamic.html.php new file mode 100755 index 0000000000..a87bd6a3a3 --- /dev/null +++ b/trunk/module/product/view/dynamic.html.php @@ -0,0 +1,56 @@ +dynamic view file of dashboard module of ZenTaoPMS. + * + * @copyright Copyright 2009-2012 青岛易软天创网络科技有限公司 (QingDao Nature Easy Soft Network Technology Co,LTD www.cnezsoft.com) + * @license LGPL (http://www.gnu.org/licenses/lgpl.html) + * @author Chunsheng Wang + * @package dashboard + * @version $Id: action->dynamic.html.php 1477 2011-03-01 15:25:50Z wwccss $ + * @link http://www.zentao.net + */ +?> + + +
      + ' . html::a(inlink('dynamic', "productID=$productID&type=today"), $lang->action->dynamic->today) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=yesterday"), $lang->action->dynamic->yesterday) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=twodaysago"), $lang->action->dynamic->twoDaysAgo) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=thisweek"), $lang->action->dynamic->thisWeek) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=lastweek"), $lang->action->dynamic->lastWeek) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=thismonth"), $lang->action->dynamic->thisMonth) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=lastmonth"), $lang->action->dynamic->lastMonth) . ''; + echo '' . html::a(inlink('dynamic', "productID=$productID&type=all"), $lang->action->dynamic->all) . ''; + echo "" . html::select('account', $users, $account, "onchange=changeUser(this.value,$productID)") . ''; + ?> +
      + + + + + + + + + + + + + + + objectType == 'case' ? 'testcase' : $action->objectType;?> + + + + + + + + + + + +
      action->date;?> action->actor;?>action->action;?> action->objectType;?> idAB;?>action->objectName;?>
      date;?>actor]) ? print($users[$action->actor]) : print($action->actor);?>actionLabel;?>action->objectTypes[$action->objectType];?>objectID;?>objectLink, $action->objectName);?>
      show();?>
      + + diff --git a/trunk/module/product/view/edit.html.php b/trunk/module/product/view/edit.html.php new file mode 100644 index 0000000000..5c5e60f433 --- /dev/null +++ b/trunk/module/product/view/edit.html.php @@ -0,0 +1,57 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + acl != 'custom') echo "class='hidden'";?>> + + + + +
      product->edit;?>
      product->name;?>name, "class='text-3'");?>
      product->code;?>code, "class='text-3'");?>
      product->PO;?>PO, "class='select-3'");?>
      product->QM;?>QM, "class='select-3'");?>
      product->RM;?>RM, "class='select-3'");?>
      product->status;?>product->statusList, $product->status, "class='select-3'");?>
      product->desc;?>desc), "rows='8' class='area-1'");?>
      product->acl;?>product->aclList, $product->acl, "onclick='setWhite(this.value);'"));?>
      product->whitelist;?>whitelist);?>
      +
      + diff --git a/trunk/module/product/view/index.html.php b/trunk/module/product/view/index.html.php new file mode 100644 index 0000000000..3dd72d3786 --- /dev/null +++ b/trunk/module/product/view/index.html.php @@ -0,0 +1,60 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + + +
      + + + + + + +
      my->home->products;?>
      + createLink('product', 'create'); + printf($lang->my->home->noProductsTip, $productLink); + ?> +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      product->name;?>story->statusList['active'] . $lang->story->common;?>story->statusList['changed'] . $lang->story->common;?>story->statusList['draft'] . $lang->story->common;?>story->statusList['closed'] . $lang->story->common;?>product->plans;?>product->releases;?>product->bugs;?>bug->unResolved;?>bug->assignToNull;?>
      createLink('product', 'view', 'product=' . $product->id), $product->name);?>stories['active']?>stories['changed']?>stories['draft']?>stories['closed']?>plans?>releases?>bugs?>unResolved;?>assignToNull;?>
      +
      + + + diff --git a/trunk/module/product/view/order.html.php b/trunk/module/product/view/order.html.php new file mode 100644 index 0000000000..dd0c448d41 --- /dev/null +++ b/trunk/module/product/view/order.html.php @@ -0,0 +1,32 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + +
      product->id?>product->name?>product->order?>
      id?>name?>id, $product->order, "size='5'")?>
      +
      + + diff --git a/trunk/module/product/view/project.html.php b/trunk/module/product/view/project.html.php new file mode 100644 index 0000000000..f2041b394b --- /dev/null +++ b/trunk/module/product/view/project.html.php @@ -0,0 +1,44 @@ + + * @package ZenTaoPMS + * @version $Id: index.html.php 2343 2011-11-21 05:24:56Z wwccss $ + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      project->name;?>project->code;?>project->end;?>project->status;?>project->totalEstimate;?>project->totalConsumed;?>project->totalLeft;?>project->progess;?>project->burn;?>
      createLink('project', 'task', 'project=' . $project->id), $project->name, '_parent');?>code;?>end;?>project->statusList[$project->status];?>hours->totalEstimate;?>hours->totalConsumed;?>hours->totalLeft;?> + hours->progress;?> height='13' text-align: /> + hours->progress;?>% + burns);?>'>
      + diff --git a/trunk/module/product/view/roadmap.html.php b/trunk/module/product/view/roadmap.html.php new file mode 100644 index 0000000000..3503464a9a --- /dev/null +++ b/trunk/module/product/view/roadmap.html.php @@ -0,0 +1,49 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + '; + foreach($years as $year) + { + $year = $year == '0000' ? $lang->future : $year . $lang->year; + echo ""; + } + echo ''; + echo ''; + foreach($years as $year) + { + echo ''; + } + echo ''; + ?> +
      product->roadmap;?>
      $year
      '; + foreach($roadmaps[$year] as $key => $roadmap) + { + if(isset($roadmap->build)) + { + echo "
      "; + echo "

      " . html::a($this->createLink('release', 'view', "releaseID=$roadmap->id"), $roadmap->name, '_blank') . '

      ' . $roadmap->date; + } + else + { + echo "
      "; + echo "

      " . html::a($this->createLink('productplan', 'view', "planID=$roadmap->id"), $roadmap->title, '_blank') . '

      ' . $roadmap->begin . ' ~ ' . $roadmap->end; + } + echo "
      "; + if(isset($roadmaps[$year][$key + 1])) echo "{$lang->downArrow}"; + } + echo '
      + diff --git a/trunk/module/product/view/view.html.php b/trunk/module/product/view/view.html.php new file mode 100644 index 0000000000..e9cd84b457 --- /dev/null +++ b/trunk/module/product/view/view.html.php @@ -0,0 +1,151 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
      + name . $this->lang->colon . $lang->product->view;?> +
      + session->productList ? $this->session->productList : inlink('browse', "productID=$product->id"); + if(!$product->deleted) + { + common::printLink('product', 'edit', "productID=$product->id", $lang->edit); + common::printLink('product', 'delete', "productID=$product->id", $lang->delete, 'hiddenwin'); + } + ?> +
      +
      +
      + product->desc;?> +
      desc;?>
      +
      + +
      + session->productList ? $this->session->productList : inlink('browse', "productID=$product->id"); + if(!$product->deleted) + { + common::printLink('product', 'edit', "productID=$product->id", $lang->edit); + common::printLink('product', 'delete', "productID=$product->id", $lang->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      +
      +
      + product->basicInfo?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      product->name;?>deleted) echo "class='deleted'";?>>name;?>
      product->code;?>code;?>
      product->PO;?>PO];?>
      product->QM;?>QM];?>
      product->RM;?>RM];?>
      product->status;?>product->statusList[$product->status];?>
      product->acl;?>product->aclList[$product->acl];?>
      product->whitelist;?> + whitelist); + foreach($whitelist as $groupID) if(isset($groups[$groupID])) echo $groups[$groupID] . ' '; + ?> +
      story->openedBy?>createdBy;?>
      story->openedDate?>createdDate;?>
      +
      +
      + product->otherInfo?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      story->statusList['active'] . $lang->story->common;?>stories['active']?>
      story->statusList['changed'] . $lang->story->common;?>stories['changed']?>
      story->statusList['draft'] . $lang->story->common;?>stories['draft']?>
      story->statusList['closed'] . $lang->story->common;?>stories['closed']?>
      product->plans?>plans?>
      product->projects?>projects?>
      product->bugs?>bugs?>
      product->docs?>docs?>
      product->cases?>cases?>
      product->bulids?>bulids?>
      product->releases?>releases?>
      +
      +
      + diff --git a/trunk/module/productplan/config.php b/trunk/module/productplan/config.php new file mode 100644 index 0000000000..f2e824cd80 --- /dev/null +++ b/trunk/module/productplan/config.php @@ -0,0 +1,6 @@ +productplan->create->requiredFields = 'title,begin,end'; +$config->productplan->edit->requiredFields = 'title,begin,end'; + +$config->productplan->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->productplan->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/trunk/module/productplan/control.php b/trunk/module/productplan/control.php new file mode 100644 index 0000000000..2a15ede131 --- /dev/null +++ b/trunk/module/productplan/control.php @@ -0,0 +1,192 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +class productplan extends control +{ + /** + * Common actions + * + * @param int $productID + * @access public + * @return void + */ + public function commonAction($productID) + { + $this->loadModel('product'); + $this->view->product = $this->product->getById($productID); + $this->view->position[] = html::a($this->createLink('product', 'browse', "productID={$this->view->product->id}"), $this->view->product->name); + $this->product->setMenu($this->product->getPairs(), $productID); + } + + /** + * Create a plan. + * + * @param int $product + * @access public + * @return void + */ + public function create($product = '') + { + if(!empty($_POST)) + { + $planID = $this->productplan->create(); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action')->create('productplan', $planID, 'opened'); + die(js::locate($this->createLink('productplan', 'browse', "product=$product"), 'parent')); + } + + $this->commonAction($product); + + $this->view->header->title = $this->lang->productplan->create; + $this->display(); + } + + /** + * Edit a plan. + * + * @param int $planID + * @access public + * @return void + */ + public function edit($planID) + { + if(!empty($_POST)) + { + $changes = $this->productplan->update($planID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('productplan', $planID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate(inlink('view', "planID=$planID"), 'parent')); + } + + $plan = $this->productplan->getByID($planID); + $this->commonAction($plan->product); + $this->view->header->title = $this->lang->productplan->edit; + $this->view->position[] = $this->lang->productplan->edit; + $this->view->plan = $plan; + $this->display(); + } + + /** + * Delete a plan. + * + * @param int $planID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($planID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->productplan->confirmDelete, $this->createLink('productPlan', 'delete', "planID=$planID&confirm=yes"))); + } + else + { + $plan = $this->productplan->getById($planID); + $this->productplan->delete(TABLE_PRODUCTPLAN, $planID); + die(js::locate(inlink('browse', "productID=$plan->product"), 'parent')); + } + } + + /** + * Browse plans. + * + * @param int $product + * @access public + * @return void + */ + public function browse($product = 0) + { + $this->session->set('productPlanList', $this->app->getURI(true)); + $this->commonAction($product); + $products = $this->product->getPairs(); + $this->view->header->title = $products[$product] . $this->lang->colon . $this->lang->productplan->browse; + $this->view->position[] = $this->lang->productplan->browse; + $this->view->plans = $this->productplan->getList($product); + $this->display(); + } + + /** + * View plan. + * + * @param int $planID + * @access public + * @return void + */ + public function view($planID = 0) + { + $this->session->set('storyList', $this->app->getURI(true)); + + $plan = $this->productplan->getByID($planID, true); + if(!$plan) die(js::error($this->lang->notFound) . js::locate('back')); + $this->commonAction($plan->product); + $products = $this->product->getPairs(); + $this->view->header->title = "PLAN #$plan->id $plan->title/" . $products[$plan->product]; + $this->view->position[] = $this->lang->productplan->view; + $this->view->planStories= $this->loadModel('story')->getPlanStories($planID); + $this->view->products = $products; + $this->view->plan = $plan; + $this->view->actions = $this->loadModel('action')->getList('productplan', $planID); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->display(); + } + + /** + * Link stories. + * + * @param int $planID + * @access public + * @return void + */ + public function linkStory($planID = 0) + { + $this->session->set('storyList', $this->app->getURI(true)); + + if(!empty($_POST)) $this->productplan->linkStory($planID); + + $plan = $this->productplan->getByID($planID); + $this->commonAction($plan->product); + $this->view->header->title = $this->lang->productplan->linkStory; + $this->view->position[] = $this->lang->productplan->linkStory; + $this->view->allStories = $this->loadModel('story')->getProductStories($this->view->product->id, $moduleID = '0', $status = 'draft,active,changed'); + $this->view->planStories= $this->story->getPlanStories($planID); + $this->view->products = $this->product->getPairs(); + $this->view->plan = $plan; + $this->view->plans = $this->dao->select('id, end')->from(TABLE_PRODUCTPLAN)->fetchPairs(); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->display(); + } + + /** + * Unlink story + * + * @param int $storyID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function unlinkStory($storyID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->productplan->confirmUnlinkStory, $this->createLink('productplan', 'unlinkstory', "storyID=$storyID&confirm=yes"))); + } + else + { + $this->productplan->unlinkStory($storyID); + die(js::reload('parent')); + } + } +} diff --git a/trunk/module/productplan/lang/en.php b/trunk/module/productplan/lang/en.php new file mode 100644 index 0000000000..de99ae5ce0 --- /dev/null +++ b/trunk/module/productplan/lang/en.php @@ -0,0 +1,38 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->productplan->common = 'Plan'; +$lang->productplan->browse = "Browse"; +$lang->productplan->index = "Index"; +$lang->productplan->create = "Create"; +$lang->productplan->edit = "Edit"; +$lang->productplan->delete = "Delete"; +$lang->productplan->view = "Info"; +$lang->productplan->linkStory = "Link story"; +$lang->productplan->unlinkStory = "Remove story"; +$lang->productplan->linkedStories = 'Stories linked'; +$lang->productplan->unlinkedStories = 'Stories unlinked'; + +$lang->productplan->confirmDelete = "Are you sure to delete this plan?"; +$lang->productplan->confirmUnlinkStory = "Are you sure to remove this story?"; + +$lang->productplan->basicInfo ='Basic Info'; + +$lang->productplan->id = 'ID'; +$lang->productplan->product = 'Product'; +$lang->productplan->title = 'Title'; +$lang->productplan->desc = 'Desc'; +$lang->productplan->begin = 'Begin'; +$lang->productplan->end = 'End'; + +$lang->productplan->placeholder->title = "shorter better."; +$lang->productplan->placeholder->begin = "begin date of this plan"; +$lang->productplan->placeholder->end = "end date of this plan"; diff --git a/trunk/module/productplan/lang/zh-cn.php b/trunk/module/productplan/lang/zh-cn.php new file mode 100644 index 0000000000..6efeac635e --- /dev/null +++ b/trunk/module/productplan/lang/zh-cn.php @@ -0,0 +1,34 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->productplan->common = '产品计划'; +$lang->productplan->browse = "浏览计划"; +$lang->productplan->index = "计划列表"; +$lang->productplan->create = "创建计划"; +$lang->productplan->edit = "编辑计划"; +$lang->productplan->delete = "删除计划"; +$lang->productplan->view = "计划详情"; +$lang->productplan->linkStory = "关联需求"; +$lang->productplan->unlinkStory = "移除需求"; +$lang->productplan->linkedStories = '已关联需求列表'; +$lang->productplan->unlinkedStories = '未关联需求列表'; + +$lang->productplan->confirmDelete = "您确认删除该计划吗?"; +$lang->productplan->confirmUnlinkStory = "您确认移除该需求吗?"; + +$lang->productplan->basicInfo ='基本信息'; + +$lang->productplan->id = '编号'; +$lang->productplan->product = '产品'; +$lang->productplan->title = '名称'; +$lang->productplan->desc = '描述'; +$lang->productplan->begin = '开始日期'; +$lang->productplan->end = '结束日期'; diff --git a/trunk/module/productplan/lang/zh-tw.php b/trunk/module/productplan/lang/zh-tw.php new file mode 100644 index 0000000000..7330681836 --- /dev/null +++ b/trunk/module/productplan/lang/zh-tw.php @@ -0,0 +1,34 @@ + + * @package productplan + * @version $Id: zh-tw.php 3238 2012-07-02 01:44:49Z wwccss $ + * @link http://www.zentao.net + */ +$lang->productplan->common = '產品計劃'; +$lang->productplan->browse = "瀏覽計劃"; +$lang->productplan->index = "計劃列表"; +$lang->productplan->create = "創建計劃"; +$lang->productplan->edit = "編輯計劃"; +$lang->productplan->delete = "刪除計劃"; +$lang->productplan->view = "計劃詳情"; +$lang->productplan->linkStory = "關聯需求"; +$lang->productplan->unlinkStory = "移除需求"; +$lang->productplan->linkedStories = '已關聯需求列表'; +$lang->productplan->unlinkedStories = '未關聯需求列表'; + +$lang->productplan->confirmDelete = "您確認刪除該計劃嗎?"; +$lang->productplan->confirmUnlinkStory = "您確認移除該需求嗎?"; + +$lang->productplan->basicInfo ='基本信息'; + +$lang->productplan->id = '編號'; +$lang->productplan->product = '產品'; +$lang->productplan->title = '名稱'; +$lang->productplan->desc = '描述'; +$lang->productplan->begin = '開始日期'; +$lang->productplan->end = '結束日期'; diff --git a/trunk/module/productplan/model.php b/trunk/module/productplan/model.php new file mode 100644 index 0000000000..d375a90251 --- /dev/null +++ b/trunk/module/productplan/model.php @@ -0,0 +1,152 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +?> +dao->findByID((int)$planID)->from(TABLE_PRODUCTPLAN)->fetch(); + if($setImgSize) $plan->desc = $this->loadModel('file')->setImgSize($plan->desc); + return $plan; + } + + /** + * Get list + * + * @param int $product + * @access public + * @return object + */ + public function getList($product = 0) + { + return $this->dao->select('*')->from(TABLE_PRODUCTPLAN)->where('product')->eq($product) + ->andWhere('deleted')->eq(0) + ->orderBy('begin')->fetchAll(); + } + + /** + * Get plan pairs. + * + * @param int $product + * @param string $expired + * @access public + * @return array + */ + public function getPairs($product = 0, $expired = '') + { + $date = date('Y-m-d'); + return array('' => '') + $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) + ->where('product')->eq((int)$product) + ->andWhere('deleted')->eq(0) + ->beginIF($expired == 'unexpired') + ->andWhere('end')->gt($date) + ->fi() + ->orderBy('begin')->fetchPairs(); + } + + /** + * Get plans for products + * + * @param int $products + * @access public + * @return void + */ + public function getForProducts($products) + { + return array('' => '') + $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) + ->where('product')->in(array_keys($products)) + ->andWhere('deleted')->eq(0) + ->orderBy('begin')->fetchPairs(); + } + + /** + * Create a plan. + * + * @access public + * @return int + */ + public function create() + { + $plan = fixer::input('post')->stripTags('title')->get(); + $this->dao->insert(TABLE_PRODUCTPLAN) + ->data($plan) + ->autoCheck() + ->batchCheck($this->config->productplan->create->requiredFields, 'notempty') + ->check('end', 'gt', $plan->begin) + ->exec(); + if(!dao::isError()) return $this->dao->lastInsertID(); + } + + /** + * Update a plan + * + * @param int $planID + * @access public + * @return array + */ + public function update($planID) + { + $oldPlan = $this->getById($planID); + $plan = fixer::input('post')->stripTags('title')->get(); + $this->dao->update(TABLE_PRODUCTPLAN) + ->data($plan) + ->autoCheck() + ->batchCheck($this->config->productplan->edit->requiredFields, 'notempty') + ->check('end', 'gt', $plan->begin) + ->where('id')->eq((int)$planID) + ->exec(); + if(!dao::isError()) return common::createChanges($oldPlan, $plan); + } + + /** + * Link stories. + * + * @param int $planID + * @access public + * @return void + */ + public function linkStory($planID) + { + $this->loadModel('story'); + $this->loadModel('action'); + foreach($this->post->stories as $storyID) + { + $this->dao->update(TABLE_STORY)->set('plan')->eq((int)$planID)->where('id')->eq((int)$storyID)->exec(); + $this->action->create('story', $storyID, 'linked2plan', '', $planID); + $this->story->setStage($storyID); + } + } + + /** + * Unlink story + * + * @param int $storyID + * @access public + * @return void + */ + public function unlinkStory($storyID) + { + $planID = $this->dao->findByID($storyID)->from(TABLE_STORY)->fields('plan')->fetch('plan'); + $this->dao->update(TABLE_STORY)->set('plan')->eq(0)->where('id')->eq((int)$storyID)->exec(); + $this->loadModel('story')->setStage($storyID); + $this->loadModel('action')->create('story', $storyID, 'unlinkedfromplan', '', $planID); + } +} diff --git a/trunk/module/productplan/view/browse.html.php b/trunk/module/productplan/view/browse.html.php new file mode 100644 index 0000000000..6f2639e352 --- /dev/null +++ b/trunk/module/productplan/view/browse.html.php @@ -0,0 +1,49 @@ + + * @package plan + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      productplan->browse;?>
      +
      id", $lang->productplan->create);?>
      +
      idAB;?>productplan->begin;?>productplan->end;?>productplan->title;?>productplan->desc;?>actions;?>
      id"), $plan->id);?>begin;?>end;?>id"), $plan->title);?>desc;?> + id", $lang->edit); + common::printLink('productplan', 'linkstory', "planID=$plan->id", $lang->productplan->linkStory); + common::printLink('productplan', 'delete', "planID=$plan->id", $lang->delete, 'hiddenwin'); + ?> +
      + diff --git a/trunk/module/productplan/view/create.html.php b/trunk/module/productplan/view/create.html.php new file mode 100644 index 0000000000..c7e59dc035 --- /dev/null +++ b/trunk/module/productplan/view/create.html.php @@ -0,0 +1,51 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      productplan->create;?>
      productplan->product;?>name;?>
      productplan->title;?>
      productplan->begin;?>
      productplan->end;?>
      productplan->desc;?>
      + id); + ?> +
      +
      + diff --git a/trunk/module/productplan/view/edit.html.php b/trunk/module/productplan/view/edit.html.php new file mode 100644 index 0000000000..fe45bcc4a9 --- /dev/null +++ b/trunk/module/productplan/view/edit.html.php @@ -0,0 +1,50 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      productplan->edit;?>
      productplan->product;?>name;?>
      productplan->title;?>title, 'class="text-3"');?>
      productplan->begin;?>begin, 'class="text-3 date"');?>
      productplan->end;?>end, 'class="text-3 date"');?>
      productplan->desc;?>desc), "rows='10' class='area-1'");?>
      + id); + ?> +
      +
      + diff --git a/trunk/module/productplan/view/linkstory.html.php b/trunk/module/productplan/view/linkstory.html.php new file mode 100644 index 0000000000..e1a995dfed --- /dev/null +++ b/trunk/module/productplan/view/linkstory.html.php @@ -0,0 +1,89 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + + + + + + id])) continue; + if($story->plan and helper::diffDate($plans[$story->plan], helper::today()) > 0) continue; + ?> + + + + + + + + + + + + + + + + + + +
      title .$lang->colon . $lang->productplan->unlinkedStories;?>
      idAB;?>priAB;?>story->plan;?>story->title;?>openedByAB;?>assignedToAB;?>story->estimateAB;?>statusAB;?>link;?>
      createLink('story', 'view', "storyID=$story->id"), $story->id);?>pri;?>planTitle;?>createLink('story', 'view', "storyID=$story->id"), $story->title);?>openedBy];?>assignedTo];?>estimate;?>story->statusList[$story->status];?>
      story->linkStory);?>
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      title .$lang->colon . $lang->productplan->linkedStories;?>
      idAB;?>priAB;?>story->title;?>openedByAB;?>assignedToAB;?>story->estimateAB;?>statusAB;?>story->stageAB;?>actions?>
      createLink('story', 'view', "storyID=$story->id"), $story->id);?>pri;?>createLink('story', 'view', "storyID=$story->id"), $story->title);?>openedBy];?>assignedTo];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?>id", $lang->productplan->unlinkStory, 'hiddenwin');?>
      + diff --git a/trunk/module/productplan/view/view.html.php b/trunk/module/productplan/view/view.html.php new file mode 100644 index 0000000000..4e59ae4c87 --- /dev/null +++ b/trunk/module/productplan/view/view.html.php @@ -0,0 +1,96 @@ + + * @package productplan + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + +
      PLAN #id . ' ' . $plan->title;?>
      +
      + productplan->desc;?> +
      desc;?>
      +
      + +
      + session->productPlanList ? $this->session->productPlanList : inlink('browse', "planID=$plan->id"); + if(!$plan->deleted) + { + common::printLink('productplan', 'edit', "planID=$plan->id", $lang->edit); + common::printLink('productplan', 'linkstory',"planID=$plan->id", $lang->productplan->linkStory); + common::printLink('productplan', 'delete', "planID=$plan->id", $lang->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      + + + + + + + + + + + + + + + + + + + createLink('story', 'view', "storyID=$story->id"); + $totalEstimate += $story->estimate; + ?> + + + + + + + + + + + + + + +
      title .$lang->colon . $lang->productplan->linkedStories;?>
      idAB;?>priAB;?>story->title;?>openedByAB;?>assignedToAB;?>story->estimateAB;?>statusAB;?>story->stageAB;?>actions?>
      id);?>pri;?>title);?>openedBy];?>assignedTo];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?>id", $lang->productplan->unlinkStory, 'hiddenwin');?>
      product->storySummary, count($planStories), $totalEstimate);?>
      +
      +
      + productplan->basicInfo?> + + + + + + + + + + +
      productplan->title;?>deleted) echo "class='deleted'";?>>title;?> +
      productplan->begin;?>begin;?> +
      productplan->end;?>end;?> +
      +
      +
      + diff --git a/trunk/module/project/config.php b/trunk/module/project/config.php new file mode 100644 index 0000000000..d088f423b1 --- /dev/null +++ b/trunk/module/project/config.php @@ -0,0 +1,78 @@ +project->defaultWorkhours = 7; +$config->project->orderBy = 'isDone, `order`, status'; + +global $lang, $app; +$app->loadLang('task'); +$config->project->create->requiredFields = 'name,code,team,begin,end'; +$config->project->edit->requiredFields = 'name,code,team,begin,end'; + +$config->project->editor->create = array('id' => 'desc,goal', 'tools' => 'simpleTools'); +$config->project->editor->edit = array('id' => 'desc,goal', 'tools' => 'simpleTools'); + +$config->project->search['module'] = 'task'; +$config->project->search['fields']['name'] = $lang->task->name; +$config->project->search['fields']['id'] = $lang->task->id; +$config->project->search['fields']['status'] = $lang->task->status; +$config->project->search['fields']['desc'] = $lang->task->desc; +$config->project->search['fields']['assignedTo'] = $lang->task->assignedTo; +$config->project->search['fields']['pri'] = $lang->task->pri; + +$config->project->search['fields']['project'] = $lang->task->project; +$config->project->search['fields']['module'] = $lang->task->module; +$config->project->search['fields']['estimate'] = $lang->task->estimate; +$config->project->search['fields']['left'] = $lang->task->left; +$config->project->search['fields']['consumed'] = $lang->task->consumed; +$config->project->search['fields']['type'] = $lang->task->type; +$config->project->search['fields']['fromBug'] = $lang->task->fromBug; +$config->project->search['fields']['closedReason'] = $lang->task->closedReason; + +$config->project->search['fields']['openedBy'] = $lang->task->openedBy; +$config->project->search['fields']['finishedBy'] = $lang->task->finishedBy; +$config->project->search['fields']['closedBy'] = $lang->task->closedBy; +$config->project->search['fields']['canceledBy'] = $lang->task->canceledBy; +$config->project->search['fields']['lastEditedBy'] = $lang->task->lastEditedBy; + +$config->project->search['fields']['mailto'] = $lang->task->mailto; + +$config->project->search['fields']['openedDate'] = $lang->task->openedDate; +$config->project->search['fields']['deadline'] = $lang->task->deadline; +$config->project->search['fields']['estStarted'] = $lang->task->estStarted; +$config->project->search['fields']['realStarted'] = $lang->task->realStarted; +$config->project->search['fields']['assignedDate'] = $lang->task->assignedDate; +$config->project->search['fields']['finishedDate'] = $lang->task->finishedDate; +$config->project->search['fields']['closedDate'] = $lang->task->closedDate; +$config->project->search['fields']['canceledDate'] = $lang->task->canceledDate; +$config->project->search['fields']['lastEditedDate'] = $lang->task->lastEditedDate; + +$config->project->search['params']['name'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->project->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->task->statusList); +$config->project->search['params']['desc'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->project->search['params']['assignedTo'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->project->search['params']['pri'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->task->priList); + +$config->project->search['params']['project'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->project->search['params']['module'] = array('operator' => 'belong', 'control' => 'select', 'values' => ''); +$config->project->search['params']['estimate'] = array('operator' => '=', 'control' => 'input', 'values' => ''); +$config->project->search['params']['left'] = array('operator' => '=', 'control' => 'input', 'values' => ''); +$config->project->search['params']['consumed'] = array('operator' => '=', 'control' => 'input', 'values' => ''); +$config->project->search['params']['type'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->task->typeList); +$config->project->search['params']['fromBug'] = array('operator' => '=', 'control' => 'input', 'values' => $lang->task->typeList); +$config->project->search['params']['closedReason'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->task->reasonList); + +$config->project->search['params']['openedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->project->search['params']['finishedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->project->search['params']['closedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->project->search['params']['cancelBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->project->search['params']['lastEditedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); + +$config->project->search['params']['mailto'] = array('operator' => 'include', 'control' => 'select', 'values' => 'users'); + +$config->project->search['params']['openedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['deadline'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['estStarted'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['realStarted'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['assignedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['finishedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['closedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->project->search['params']['canceledDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); diff --git a/trunk/module/project/control.php b/trunk/module/project/control.php new file mode 100644 index 0000000000..3fa93d1a0f --- /dev/null +++ b/trunk/module/project/control.php @@ -0,0 +1,1300 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +class project extends control +{ + public $projects; + + /** + * Construct function, Set projects. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + if($this->methodName != 'computeburn') + { + $this->projects = $this->project->getPairs(); + if(!$this->projects and $this->methodName != 'create') $this->locate($this->createLink('project', 'create')); + } + } + + /** + * The index page. + * + * @param string $locate yes|no locate to the browse page or not. + * @param string $status the projects status, if locate is no, then get projects by the $status. + * @access public + * @return void + */ + public function index($locate = 'yes', $status = 'undone') + { + if(empty($this->projects)) $this->locate($this->createLink('project', 'create')); + if($locate == 'yes') $this->locate($this->createLink('project', 'browse')); + + $this->app->loadLang('my'); + $this->view->projectStats = $this->project->getProjectStats($status); + + $this->display(); + } + + /** + * Browse a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function browse($projectID = 0) + { + $this->locate($this->createLink($this->moduleName, 'task', "projectID=$projectID")); + } + + /** + * Common actions. + * + * @param int $projectID + * @access public + * @return object current object + */ + public function commonAction($projectID = 0) + { + $this->loadModel('product'); + + /* Get projects and products info. */ + $projectID = $this->project->saveState($projectID, array_keys($this->projects)); + $project = $this->project->getById($projectID); + $products = $this->project->getProducts($project->id); + $childProjects = $this->project->getChildProjects($project->id); + $teamMembers = $this->project->getTeamMembers($project->id); + + /* Set menu. */ + $this->project->setMenu($this->projects, $project->id); + + /* Assign. */ + $this->view->projects = $this->projects; + $this->view->project = $project; + $this->view->childProjects = $childProjects; + $this->view->products = $products; + $this->view->teamMembers = $teamMembers; + + return $project; + } + + /** + * Tasks of a project. + * + * @param int $projectID + * @param string $status + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function task($projectID = 0, $status = 'all', $param = 0, $orderBy = '', $recTotal = 0, $recPerPage = 100, $pageID = 1) + { + $this->loadModel('tree'); + + /* Set browseType, productID, moduleID and queryID. */ + $browseType = strtolower($status); + $queryID = ($browseType == 'bysearch') ? (int)$param : 0; + $moduleID = ($status == 'byModule') ? (int)$param : 0; + $project = $this->commonAction($projectID); + $projectID = $project->id; + + /* Save to session. */ + $uri = $this->app->getURI(true); + $this->app->session->set('taskList', $uri); + $this->app->session->set('storyList', $uri); + $this->app->session->set('projectList', $uri); + + /* Process the order by field. */ + if(!$orderBy) $orderBy = $this->cookie->projectTaskOrder ? $this->cookie->projectTaskOrder : 'status,id_desc'; + setcookie('projectTaskOrder', $orderBy, $this->config->cookieLife, $this->config->webRoot); + + /* Header and position. */ + $this->view->header->title = $project->name . $this->lang->colon . $this->lang->project->task; + $this->view->position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $this->view->position[] = $this->lang->project->task; + + /* Load pager and get tasks. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $tasks = array(); + if($status == 'byModule') + { + $tasks = $this->loadModel('task')->getTasksByModule($projectID, $this->tree->getAllChildID($moduleID), $orderBy, $pager); + } + elseif($browseType != "bysearch") + { + $status = $status == 'byProject' ? 'all' : $status; + $tasks = $this->loadModel('task')->getProjectTasks($projectID, $status, $orderBy, $pager); + } + else + { + if($queryID) + { + $query = $this->loadModel('search')->getQuery($queryID); + if($query) + { + $this->session->set('taskQuery', $query->sql); + $this->session->set('taskForm', $query->form); + } + else + { + $this->session->set('taskQuery', ' 1 = 1'); + } + } + else + { + if($this->session->taskQuery == false) $this->session->set('taskQuery', ' 1 = 1'); + } + $taskQuery = str_replace("`project` = 'all'", '1', $this->session->taskQuery); // Search all project. + $this->session->set('taskQueryCondition', $taskQuery); + $this->session->set('taskOrderBy', $orderBy); + $tasks = $this->project->getSearchTasks($taskQuery, $pager, $orderBy); + } + + /* Build the search form. */ + $this->config->project->search['actionURL'] = $this->createLink('project', 'task', "projectID=$projectID&status=bySearch¶m=myQueryID"); + $this->config->project->search['queryID'] = $queryID; + $this->config->project->search['params']['project']['values'] = array(''=>'', $projectID => $this->projects[$projectID], 'all' => $this->lang->project->allProject); + $this->config->project->search['params']['module']['values'] = $this->tree->getOptionMenu($projectID, $viewType = 'task', $startModuleID = 0); + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->project->search); + + /* Assign. */ + $this->view->tasks = $tasks; + $this->view->tabID = 'task'; + $this->view->pager = $pager; + $this->view->recTotal = $pager->recTotal; + $this->view->recPerPage = $pager->recPerPage; + $this->view->orderBy = $orderBy; + $this->view->browseType = $browseType; + $this->view->status = $status; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->param = $param; + $this->view->projectID = $projectID; + $this->view->project = $project; + $this->view->moduleID = $moduleID; + $this->view->moduleTree = $this->tree->getTreeMenu($projectID, $viewType = 'task', $startModuleID = 0, array('treeModel', 'createTaskLink')); + $this->view->projectTree = $this->project->tree(); + + $this->display(); + } + + /** + * Browse tasks in group. + * + * @param int $projectID + * @param string $groupBy the field to group by + * @access public + * @return void + */ + public function grouptask($projectID = 0, $groupBy = 'story') + { + $project = $this->commonAction($projectID); + $projectID = $project->id; + + /* Save session. */ + $this->app->session->set('taskList', $this->app->getURI(true)); + $this->app->session->set('storyList', $this->app->getURI(true)); + + /* Header and session. */ + $this->view->header['title'] = $project->name . $this->lang->colon . $this->lang->project->task; + $this->view->position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $this->view->position[] = $this->lang->project->task; + + /* Get tasks and group them. */ + $tasks = $this->loadModel('task')->getProjectTasks($projectID, $status = 'all', $groupBy ? $groupBy : 'story'); + $groupBy = strtolower(str_replace('`', '', $groupBy)); + $taskLang = $this->lang->task; + $groupByList = array(); + $groupTasks = array(); + + /* Get users. */ + $users = $this->loadModel('user')->getPairs('noletter'); + foreach($tasks as $task) + { + if($groupBy == '') + { + $groupTasks[$task->story][] = $task; + $groupByList[$task->story] = $task->storyTitle; + } + elseif($groupBy == 'story') + { + $groupTasks[$task->story][] = $task; + $groupByList[$task->story] = $task->storyTitle; + } + elseif($groupBy == 'status') + { + $groupTasks[$taskLang->statusList[$task->status]][] = $task; + } + elseif($groupBy == 'assignedto') + { + $groupTasks[$task->assignedToRealName][] = $task; + } + elseif($groupBy == 'openedby') + { + $groupTasks[$users[$task->openedBy]][] = $task; + } + elseif($groupBy == 'finishedby') + { + $groupTasks[$users[$task->finishedBy]][] = $task; + } + elseif($groupBy == 'closedby') + { + $groupTasks[$users[$task->closedBy]][] = $task; + } + elseif($groupBy == 'type') + { + $groupTasks[$taskLang->typeList[$task->type]][] = $task; + } + else + { + $groupTasks[$task->$groupBy][] = $task; + } + } + + /* Assign. */ + $this->view->members = $this->project->getTeamMembers($projectID); + $this->view->tasks = $groupTasks; + $this->view->tabID = 'task'; + $this->view->groupByList = $groupByList; + $this->view->browseType = 'group'; + $this->view->groupBy = $groupBy; + $this->view->orderBy = $groupBy; + $this->view->projectID = $projectID; + $this->view->users = $users; + $this->display(); + } + + /** + * Import tasks undoned from other projects. + * + * @param int $projectID + * @access public + * @return void + */ + public function importTask($toProject, $fromProject = 0) + { + if(!empty($_POST)) + { + $this->project->importTask($toProject,$fromProject); + die(js::locate(inlink('task', "projectID=$toProject"), 'parent')); + } + + $project = $this->commonAction($toProject); + $projects = $this->project->getPairs('all'); + unset($projects[$toProject]); + + /* Save session. */ + $this->app->session->set('taskList', $this->app->getURI(true)); + $this->app->session->set('storyList', $this->app->getURI(true)); + + $this->view->header->title = $project->name . $this->lang->colon . $this->lang->project->importTask; + $this->view->position[] = html::a(inlink('browse', "projectID=$toProject"), $project->name); + $this->view->position[] = $this->lang->project->importTask; + $this->view->tasks2Imported = $this->project->getTasks2Imported($fromProject); + $this->view->projects = $projects; + $this->view->projectID = $project->id; + $this->view->fromProject = $fromProject; + $this->display(); + } + + /** + * Import from Bug. + * + * @param int $projectID + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function importBug($projectID = 0, $browseType = 'all', $param = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1) + { + if(!empty($_POST)) + { + $mails = $this->project->importBug($projectID); + if(dao::isError()) die(js::error(dao::getError())); + + foreach($mails as $mail) $this->sendmail($mail->taskID, $mail->actionID); + + /* Locate the browser. */ + die(js::locate($this->createLink('project', 'importBug', "projectID=$projectID"), 'parent')); + } + + /* Set browseType, productID, moduleID and queryID. */ + $browseType = strtolower($browseType); + $queryID = ($browseType == 'bysearch') ? (int)$param : 0; + + /* Save to session. */ + $uri = $this->app->getURI(true); + $this->app->session->set('bugList', $uri); + $this->app->session->set('storyList', $uri); + $this->app->session->set('projectList', $uri); + + $this->loadModel('bug'); + $projects = $this->project->getPairs(); + $this->project->setMenu($projects, $projectID); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $header['title'] = $projects[$projectID] . $this->lang->colon . $this->lang->project->importBug; + $position[] = html::a($this->createLink('project', 'task', "projectID=$projectID"), $projects[$projectID]); + $position[] = $this->lang->project->importBug; + + /* Get users, products and projects.*/ + $users = $this->project->getTeamMemberPairs($projectID, 'nodeleted'); + $products = $this->dao->select('t1.product, t2.name')->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t1.project')->eq($projectID) + ->fetchPairs('product'); + if(!empty($products)) + { + unset($projects); + $projects = $this->dao->select('t1.project, t2.name')->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.product')->in(array_keys($products)) + ->fetchPairs('project'); + } + else + { + $projectName = $projects[$projectID]; + unset($projects); + $projects[$projectID] = $projectName; + } + + /* Get bugs.*/ + $bugs = array(); + if($browseType != "bysearch") + { + $bugs = $this->bug->getActiveBugs($pager, $projectID, array_keys($products)); + } + else + { + if($queryID) + { + $query = $this->loadModel('search')->getQuery($queryID); + if($query) + { + $this->session->set('bugQuery', $query->sql); + $this->session->set('bugForm', $query->form); + } + else + { + $this->session->set('bugQuery', ' 1 = 1'); + } + } + else + { + if($this->session->bugQuery == false) $this->session->set('bugQuery', ' 1 = 1'); + } + $bugQuery = str_replace("`product` = 'all'", "`product`" . helper::dbIN(array_keys($products)), $this->session->bugQuery); // Search all project. + $bugs = $this->project->getSearchBugs($products, $projectID, $bugQuery, $pager, 'id_desc'); + } + + /* Build the search form. */ + $this->config->bug->search['actionURL'] = $this->createLink('project', 'importBug', "projectID=$projectID&browseType=bySearch¶m=myQueryID"); + $this->config->bug->search['queryID'] = $queryID; + if(!empty($products)) + { + $this->config->bug->search['params']['product']['values'] = array(''=>'') + $products + array('all'=>$this->lang->project->aboveAllProduct); + } + else + { + $this->config->bug->search['params']['product']['values'] = array(''=>''); + } + $this->config->bug->search['params']['project']['values'] = array(''=>'') + $projects + array('all'=>$this->lang->project->aboveAllProject); + unset($this->config->bug->search['fields']['resolvedBy']); + unset($this->config->bug->search['fields']['closedBy']); + unset($this->config->bug->search['fields']['status']); + unset($this->config->bug->search['fields']['toTask']); + unset($this->config->bug->search['fields']['toStory']); + unset($this->config->bug->search['fields']['severity']); + unset($this->config->bug->search['fields']['resolution']); + unset($this->config->bug->search['fields']['resolvedBuild']); + unset($this->config->bug->search['fields']['resolvedDate']); + unset($this->config->bug->search['fields']['closedDate']); + unset($this->config->bug->search['params']['resolvedBy']); + unset($this->config->bug->search['params']['closedBy']); + unset($this->config->bug->search['params']['status']); + unset($this->config->bug->search['params']['toTask']); + unset($this->config->bug->search['params']['toStory']); + unset($this->config->bug->search['params']['severity']); + unset($this->config->bug->search['params']['resolution']); + unset($this->config->bug->search['params']['resolvedBuild']); + unset($this->config->bug->search['params']['resolvedDate']); + unset($this->config->bug->search['params']['closedDate']); + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->bug->search); + + /* Assign. */ + $this->view->header = $header; + $this->view->pager = $pager; + $this->view->bugs = $bugs; + $this->view->recTotal = $pager->recTotal; + $this->view->recPerPage = $pager->recPerPage; + $this->view->browseType = $browseType; + $this->view->param = $param; + $this->view->users = $users; + $this->view->projectID = $projectID; + $this->display(); + } + + /** + * Browse stories of a project. + * + * @param int $projectID + * @param string $orderBy + * @access public + * @return void + */ + public function story($projectID = 0, $orderBy = '') + { + /* Load these models. */ + $this->loadModel('story'); + $this->loadModel('user'); + $this->loadModel('task'); + + /* Save session. */ + $this->app->session->set('storyList', $this->app->getURI(true)); + + /* Process the order by field. */ + if(!$orderBy) $orderBy = $this->cookie->projectStoryOrder ? $this->cookie->projectStoryOrder : 'pri'; + setcookie('projectStoryOrder', $orderBy, $this->config->cookieLife, $this->config->webRoot); + + $project = $this->commonAction($projectID); + + /* Header and position. */ + $header['title'] = $project->name . $this->lang->colon . $this->lang->project->story; + $position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $position[] = $this->lang->project->story; + + /* The pager. */ + $stories = $this->story->getProjectStories($projectID, $orderBy); + $storyTasks = $this->task->getStoryTaskCounts(array_keys($stories), $projectID); + $users = $this->user->getPairs('noletter'); + + /* Save storyIDs session for get the pre and next story. */ + $storyIDs = ''; + foreach($stories as $story) $storyIDs .= ',' . $story->id; + $this->session->set('storyIDs', $storyIDs . ','); + + /* Get project's product. */ + $productID = 0; + $products = $this->loadModel('product')->getProductsByProject($projectID); + if($products) $productID = key($products); + + /* Assign. */ + $this->view->header = $header; + $this->view->position = $position; + $this->view->productID = $productID; + $this->view->stories = $stories; + $this->view->orderBy = $orderBy; + $this->view->storyTasks = $storyTasks; + $this->view->tabID = 'story'; + $this->view->users = $users; + + $this->display(); + } + + /** + * Browse bugs of a project. + * + * @param int $projectID + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function bug($projectID = 0, $orderBy = 'status,id_desc', $build = 0, $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Load these two models. */ + $this->loadModel('bug'); + $this->loadModel('user'); + + /* Save session. */ + $this->session->set('bugList', $this->app->getURI(true)); + + $project = $this->commonAction($projectID); + $products = $this->project->getProducts($project->id); + $productID = key($products); // Get the first product for creating bug. + + /* Header and position. */ + $header['title'] = $project->name . $this->lang->colon . $this->lang->project->bug; + $position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $position[] = $this->lang->project->bug; + + /* Load pager and get bugs, user. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + $bugs = $this->bug->getProjectBugs($projectID, $orderBy, $pager, $build); + $users = $this->user->getPairs('noletter'); + + /* Assign. */ + $this->view->header = $header; + $this->view->position = $position; + $this->view->bugs = $bugs; + $this->view->tabID = 'bug'; + $this->view->build = $this->loadModel('build')->getById($build); + $this->view->buildID = $this->view->build ? $this->view->build->id : 0; + $this->view->pager = $pager; + $this->view->orderBy = $orderBy; + $this->view->users = $users; + $this->view->productID = $productID; + + $this->display(); + } + + /** + * Browse builds of a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function build($projectID = 0) + { + $this->loadModel('testtask'); + $this->session->set('buildList', $this->app->getURI(true)); + + $project = $this->commonAction($projectID); + + /* Header and position. */ + $this->view->header->title = $project->name . $this->lang->colon . $this->lang->project->build; + $this->view->position[] = html::a(inlink('browse', "projectID=$projectID"), $project->name); + $this->view->position[] = $this->lang->project->build; + + /* Get builds. */ + $this->view->builds = $this->loadModel('build')->getProjectBuilds((int)$projectID); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + + $this->display(); + } + + /** + * Browse test tasks of project. + * + * @param int $projectID + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function testtask($projectID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $this->loadModel('testtask'); + /* Save session. */ + $this->session->set('testtaskList', $this->app->getURI(true)); + + $project = $this->commonAction($projectID); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + $this->view->header->title = $this->projects[$projectID] . $this->lang->colon . $this->lang->testtask->common; + $this->view->position[] = html::a($this->createLink('project', 'testtask', "projectID=$projectID"), $this->projects[$projectID]); + $this->view->position[] = $this->lang->testtask->common; + $this->view->projectID = $projectID; + $this->view->projectName = $this->projects[$projectID]; + $this->view->pager = $pager; + $this->view->orderBy = $orderBy; + $this->view->tasks = $this->testtask->getProjectTasks($projectID); + $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter'); + + $this->display(); + } + + /** + * Browse burndown chart of a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function burn($projectID = 0) + { + $this->loadModel('report'); + $project = $this->commonAction($projectID); + + /* Header and position. */ + $header['title'] = $project->name . $this->lang->colon . $this->lang->project->burn; + $position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $position[] = $this->lang->project->burn; + + /* Create charts by flash. */ + //$dataXML = $this->report->createSingleXML($this->project->getBurnData($project->id), $this->lang->project->charts->burn->graph, $this->lang->report->singleColor); + //$charts = $this->report->createJSChart('line', $dataXML, 700, 350); + + /* Create charts by flot. */ + $sets = $this->project->getBurnDataFlot($project->id); + $count = $sets['count']; + unset($sets['count']); + $dataJSON = $this->report->createSingleJSON($sets); + $charts = $this->report->createJSChartFlot($project->name, $dataJSON, $count, 700, 350); + + /* Assign. */ + $this->view->header = $header; + $this->view->position = $position; + $this->view->tabID = 'burn'; + $this->view->charts = $charts; + $this->view->projectID = $projectID; + + $this->display(); + } + + /** + * Get data of burndown chart. + * + * @param int $projectID + * @access public + * @return void + */ + public function burnData($projectID = 0) + { + $this->loadModel('report'); + $sets = $this->project->getBurnData($projectID); + die($this->report->createSingleXML($sets, $this->lang->project->charts->burn->graph)); + } + + /** + * Compute burndown datas. + * + * @param string $reload + * @access public + * @return void + */ + public function computeBurn($reload = 'no') + { + $this->view->burns = $this->project->computeBurn(); + if($reload == 'yes') die(js::reload('parent')); + die($this->display()); + } + + /** + * Browse team of a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function team($projectID = 0) + { + $project = $this->commonAction($projectID); + + $header['title'] = $project->name . $this->lang->colon . $this->lang->project->team; + $position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $position[] = $this->lang->project->team; + + $this->view->header = $header; + $this->view->position = $position; + + $this->display(); + } + + /** + * Docs of a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function doc($projectID) + { + $this->project->setMenu($this->projects, $projectID); + $this->session->set('docList', $this->app->getURI(true)); + + $project = $this->dao->findById($projectID)->from(TABLE_PROJECT)->fetch(); + $this->view->header->title = $this->lang->project->doc; + $this->view->position[] = html::a($this->createLink($this->moduleName, 'browse'), $project->name); + $this->view->position[] = $this->lang->project->doc; + $this->view->project = $project; + $this->view->docs = $this->loadModel('doc')->getProjectDocs($projectID); + $this->view->modules = $this->doc->getProjectModulePairs(); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->display(); + } + + /** + * Create a project. + * + * @access public + * @return void + */ + public function create($projectID = '', $copyProjectID = '') + { + if($projectID) + { + $this->view->tips = $this->fetch('project', 'tips', "projectID=$projectID"); + $this->view->projectID = $projectID; + $this->display(); + exit; + } + + $name = ''; + $code = ''; + $team = ''; + $products = ''; + $whitelist = ''; + $acl = 'open'; + + if($copyProjectID) + { + $copyProject = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($copyProjectID)->fetch(); + $name = $copyProject->name; + $code = $copyProject->code; + $team = $copyProject->team; + $acl = $copyProject->acl; + $whitelist = $copyProject->whitelist; + $products = join(',', array_keys($this->project->getProducts($copyProjectID))); + } + + if(!empty($_POST)) + { + $projectID = $copyProjectID == '' ? $this->project->create() : $this->project->create($copyProjectID); + $this->project->updateProducts($projectID); + if(dao::isError()) die(js::error(dao::getError())); + + $this->loadModel('action')->create('project', $projectID, 'opened'); + die(js::locate($this->createLink('project', 'create', "projectID=$projectID"), 'parent')); + } + + $this->project->setMenu($this->projects, key($this->projects)); + + $this->view->header->title = $this->lang->project->create; + $this->view->position[] = $this->view->header->title; + $this->view->projects = array('' => '') + $this->projects; + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->allProducts = $this->loadModel('product')->getPairs(); + $this->view->name = $name; + $this->view->code = $code; + $this->view->team = $team; + $this->view->products = $products ; + $this->view->whitelist = $whitelist; + $this->view->acl = $acl ; + $this->view->copyProjectID = $copyProjectID; + $this->display(); + } + + /** + * Edit a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function edit($projectID) + { + $browseProjectLink = $this->createLink('project', 'browse', "projectID=$projectID"); + if(!empty($_POST)) + { + $changes = $this->project->update($projectID); + $this->project->updateProducts($projectID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('project', $projectID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate($this->createLink('project', 'view', "projectID=$projectID"), 'parent')); + } + + /* Set menu. */ + $this->project->setMenu($this->projects, $projectID); + + $projects = array('' => '') + $this->projects; + $project = $this->project->getById($projectID); + $managers = $this->project->getDefaultManagers($projectID); + + /* Remove current project from the projects. */ + unset($projects[$projectID]); + + $header['title'] = $this->lang->project->edit . $this->lang->colon . $project->name; + $position[] = html::a($browseProjectLink, $project->name); + $position[] = $this->lang->project->edit; + + $linkedProducts = $this->project->getProducts($project->id); + $linkedProducts = join(',', array_keys($linkedProducts)); + + $this->view->header = $header; + $this->view->position = $position; + $this->view->projects = $projects; + $this->view->project = $project; + $this->view->users = $this->loadModel('user')->getPairs('noclosed,nodeleted'); + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->allProducts = $this->loadModel('product')->getPairs(); + $this->view->linkedProducts = $linkedProducts; + + $this->display(); + } + + /** + * View a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function view($projectID) + { + $project = $this->project->getById($projectID, true); + if(!$project) die(js::error($this->lang->notFound) . js::locate('back')); + + /* Set menu. */ + $this->project->setMenu($this->projects, $project->id); + + $this->view->header->title = $this->lang->project->view; + $this->view->position[] = $this->view->header->title; + + $this->view->project = $project; + $this->view->products = $this->project->getProducts($project->id); + $this->view->groups = $this->loadModel('group')->getPairs(); + $this->view->actions = $this->loadModel('action')->getList('project', $projectID); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + + $this->display(); + } + + /** + * Delete a project. + * + * @param int $projectID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($projectID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm(sprintf($this->lang->project->confirmDelete, $this->projects[$projectID]), $this->createLink('project', 'delete', "projectID=$projectID&confirm=yes")); + exit; + } + else + { + $this->project->delete(TABLE_PROJECT, $projectID); + $this->session->set('project', ''); + die(js::locate(inlink('index'), 'parent')); + } + } + + /** + * Order project + * + * @param int $projectID + * @access public + * @return void + */ + public function order($projectID) + { + if($_POST) + { + $this->project->saveOrder(); + die(js::reload('parent')); + } + $project = $this->commonAction($projectID); + $this->project->setMenu($this->projects, $project->id); + $this->view->projects = $this->project->getList(); + $this->view->projectID = $project->id; + $this->display(); + } + + /** + * Send email. + * + * @param int $taskID + * @param int $actionID + * @access public + * @return void + */ + public function sendmail($taskID, $actionID) + { + /* Set toList and ccList. */ + $task = $this->loadModel('task')->getById($taskID); + $projectName = $this->project->getById($task->project)->name; + $toList = $task->assignedTo; + $ccList = trim($task->mailto, ','); + + if($toList == '') + { + if($ccList == '') return; + if(strpos($ccList, ',') === false) + { + $toList = $ccList; + $ccList = ''; + } + else + { + $commaPos = strpos($ccList, ','); + $toList = substr($ccList, 0, $commaPos); + $ccList = substr($ccList, $commaPos + 1); + } + } + elseif(strtolower($toList) == 'closed') + { + $toList = $task->finishedBy; + } + + /* Get action info. */ + $action = $this->loadModel('action')->getById($actionID); + $history = $this->action->getHistory($actionID); + $action->history = isset($history[$actionID]) ? $history[$actionID] : array(); + + /* Create the email content. */ + $this->view->task = $task; + $this->view->action = $action; + $this->clear(); + $mailContent = $this->parse($this->moduleName, 'sendmail'); + + /* Send emails. */ + $this->loadModel('mail')->send($toList, $projectName . ':' . 'TASK#' . $task->id . $this->lang->colon . $task->name, $mailContent, $ccList); + if($this->mail->isError()) echo js::error($this->mail->getError()); + } + + /** + * Manage products. + * + * @param int $projectID + * @access public + * @return void + */ + public function manageProducts($projectID) + { + $browseProjectLink = $this->createLink('project', 'browse', "projectID=$projectID"); + if(!empty($_POST)) + { + $this->project->updateProducts($projectID); + if(dao::isError()) dis(js::error(dao::getError())); + die(js::locate($browseProjectLink)); + } + + $this->loadModel('product'); + $project = $this->project->getById($projectID); + + /* Set menu. */ + $this->project->setMenu($this->projects, $project->id); + + /* Title and position. */ + $header['title'] = $this->lang->project->manageProducts . $this->lang->colon . $project->name; + $position[] = html::a($browseProjectLink, $project->name); + $position[] = $this->lang->project->manageProducts; + + $allProducts = $this->product->getPairs(); + $linkedProducts = $this->project->getProducts($project->id); + $linkedProducts = join(',', array_keys($linkedProducts)); + + /* Assign. */ + $this->view->header = $header; + $this->view->position = $position; + $this->view->allProducts = $allProducts; + $this->view->linkedProducts = $linkedProducts; + + $this->display(); + } + + /** + * Manage childs projects. + * + * @param int $projectID + * @access public + * @return void + */ + public function manageChilds($projectID) + { + $browseProjectLink = $this->createLink('project', 'browse', "projectID=$projectID"); + if(!empty($_POST)) + { + $this->project->updateChilds($projectID); + die(js::locate($browseProjectLink)); + } + $project = $this->project->getById($projectID); + $projects = $this->projects; + unset($projects[$projectID]); + unset($projects[$project->parent]); + if(empty($projects)) $this->locate($browseProjectLink); + + /* Header and position. */ + $header['title'] = $this->lang->project->manageChilds . $this->lang->colon . $project->name; + $position[] = html::a($browseProjectLink, $project->name); + $position[] = $this->lang->project->manageChilds; + + $childProjects = $this->project->getChildProjects($project->id); + $childProjects = join(",", array_keys($childProjects)); + + /* Set menu. */ + $this->project->setMenu($this->projects, $project->id); + + /* Assign. */ + $this->view->header = $header; + $this->view->position = $position; + $this->view->projects = $projects; + $this->view->childProjects = $childProjects; + + $this->display(); + } + + /** + * Manage members of the project. + * + * @param int $projectID + * @access public + * @return void + */ + public function manageMembers($projectID = 0) + { + if(!empty($_POST)) + { + $this->project->manageMembers($projectID); + $this->locate($this->createLink('project', 'team', "projectID=$projectID")); + exit; + } + $this->loadModel('user'); + + $project = $this->project->getById($projectID); + $users = $this->user->getPairs('noclosed, nodeleted'); + $users = array('' => '') + $users; + $members = $this->project->getTeamMembers($projectID); + + /* The deleted members. */ + foreach($members as $account => $member) + { + if(!@$users[$member->account]) $member->account .= $this->lang->user->deleted; + } + + /* Set menu. */ + $this->project->setMenu($this->projects, $project->id); + + $header['title'] = $this->lang->project->manageMembers . $this->lang->colon . $project->name; + $position[] = html::a($this->createLink('project', 'browse', "projectID=$projectID"), $project->name); + $position[] = $this->lang->project->manageMembers; + $this->view->header = $header; + $this->view->position = $position; + + $this->view->project = $project; + $this->view->users = $users; + $this->view->members = $members; + $this->display(); + } + + /** + * Unlink a memeber. + * + * @param int $projectID + * @param string $account + * @param string $confirm yes|no + * @access public + * @return void + */ + public function unlinkMember($projectID, $account, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->project->confirmUnlinkMember, $this->inlink('unlinkMember', "projectID=$projectID&account=$account&confirm=yes"))); + } + else + { + $this->project->unlinkMember($projectID, $account); + die(js::locate($this->inlink('team', "projectID=$projectID"), 'parent')); + } + } + + /** + * Link stories to a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function linkStory($projectID = 0, $browseType = '', $param = 0) + { + $this->loadModel('story'); + $this->loadModel('product'); + + /* Get projects and products. */ + $project = $this->project->getById($projectID); + $products = $this->project->getProducts($projectID); + $browseLink = $this->createLink('project', 'story', "projectID=$projectID"); + + $this->session->set('storyList', $this->app->getURI(true)); // Save session. + $this->project->setMenu($this->projects, $project->id); // Set menu. + + if(empty($products)) + { + echo js::alert($this->lang->project->errorNoLinkedProducts); + die(js::locate($this->createLink('project', 'manageproducts', "projectID=$projectID"))); + } + + if(!empty($_POST)) + { + $this->project->linkStory($projectID); + die(js::locate($browseLink, 'parent')); + exit; + } + + $queryID = ($browseType == 'bySearch') ? (int)$param : 0; + + /* Build search form. */ + unset($this->config->product->search['fields']['module']); + $this->config->product->search['actionURL'] = $this->createLink('project', 'linkStory', "projectID=$projectID&browseType=bySearch&queryID=myQueryID"); + $this->config->product->search['queryID'] = $queryID; + $this->config->product->search['params']['product']['values'] = $products + array('all' => $this->lang->product->allProductsOfProject); + $this->config->product->search['params']['plan']['values'] = $this->loadModel('productplan')->getForProducts($products); + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->product->search); + + $header['title'] = $project->name . $this->lang->colon . $this->lang->project->linkStory; + $position[] = html::a($browseLink, $project->name); + $position[] = $this->lang->project->linkStory; + + if($browseType == 'bySearch') + { + $allStories = $this->story->getBySearch('', $queryID, 'id', null, $projectID); + } + else + { + $allStories = $this->story->getProductStories(array_keys($products), $moduleID = '0', $status = 'active'); + } + $prjStories = $this->story->getProjectStoryPairs($projectID); + + $this->view->header = $header; + $this->view->position = $position; + $this->view->project = $project; + $this->view->products = $products; + $this->view->allStories = $allStories; + $this->view->prjStories = $prjStories; + $this->view->browseType = $browseType; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->display(); + } + + /** + * Unlink a story. + * + * @param int $projectID + * @param int $storyID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function unlinkStory($projectID, $storyID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm($this->lang->project->confirmUnlinkStory, $this->createLink('project', 'unlinkstory', "projectID=$projectID&storyID=$storyID&confirm=yes")); + exit; + } + else + { + $this->project->unlinkStory($projectID, $storyID); + echo js::locate($this->app->session->storyList, 'parent'); + exit; + } + } + + /** + * Project dynamic. + * + * @param string $type + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function dynamic($projectID = 0, $type = 'today', $param = '', $orderBy = 'date_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $uri = $this->app->getURI(true); + $this->session->set('productList', $uri); + $this->session->set('productPlanList', $uri); + $this->session->set('releaseList', $uri); + $this->session->set('storyList', $uri); + $this->session->set('projectList', $uri); + $this->session->set('taskList', $uri); + $this->session->set('buildList', $uri); + $this->session->set('bugList', $uri); + $this->session->set('caseList', $uri); + $this->session->set('testtaskList', $uri); + + /* Set the menu. If the projectID = 0, use the indexMenu instead. */ + $this->project->setMenu($this->projects, $projectID); + if($projectID == 0) + { + $this->projects = array('0' => $this->lang->project->selectProject) + $this->projects; + unset($this->lang->project->menu); + $this->lang->project->menu = $this->lang->project->indexMenu; + $this->lang->project->menu->list = $this->project->select($this->projects, 0, 'project', 'dynamic'); + } + + /* Set the pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; + + /* Set the user and type. */ + $account = $type == 'account' ? $param : 'all'; + $period = $type == 'account' ? 'all' : $type; + + /* The header and position. */ + $project = $this->project->getByID($projectID); + $this->view->header->title = $project->name . $this->lang->colon . $this->lang->project->dynamic; + $this->view->position[] = $this->lang->project->dynamic; + + /* Assign. */ + $this->view->projectID = $projectID; + $this->view->type = $type; + $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noletter'); + $this->view->account = $account; + $this->view->actions = $this->loadModel('action')->getDynamic($account, $period, $orderBy, $pager, 'all', $projectID); + $this->display(); + } + + /** + * AJAX: get products of a project in html select. + * + * @param int $projectID + * @access public + * @return void + */ + public function ajaxGetProducts($projectID) + { + $products = $this->project->getProducts($projectID); + die(html::select('product', $products, '', 'class="select-3"')); + } + + /** + * When create a project, help the user. + * + * @param int $projectID + * @access public + * @return void + */ + public function tips($projectID) + { + $this->view->projectID = $projectID; + $this->display('project', 'tips'); + } +} diff --git a/trunk/module/project/css/bug.css b/trunk/module/project/css/bug.css new file mode 100644 index 0000000000..fa1e337bb7 --- /dev/null +++ b/trunk/module/project/css/bug.css @@ -0,0 +1,2 @@ +caption {margin-bottom:10px} +caption a{padding:5px} diff --git a/trunk/module/project/css/common.css b/trunk/module/project/css/common.css new file mode 100644 index 0000000000..980dc627c0 --- /dev/null +++ b/trunk/module/project/css/common.css @@ -0,0 +1,2 @@ +#productsBox span{display:block; float:left; width:250px; overflow:hidden; word-break:keep-all; white-space:nowrap} +#whitelistBox span{display:block; float:left; width:150px; overflow:hidden; word-break:keep-all; white-space:nowrap} diff --git a/trunk/module/project/css/grouptask.css b/trunk/module/project/css/grouptask.css new file mode 100644 index 0000000000..9988dddd2f --- /dev/null +++ b/trunk/module/project/css/grouptask.css @@ -0,0 +1 @@ +.groupdivider{border-bottom:1px solid black} diff --git a/trunk/module/project/css/task.css b/trunk/module/project/css/task.css new file mode 100644 index 0000000000..534fe13d0c --- /dev/null +++ b/trunk/module/project/css/task.css @@ -0,0 +1,4 @@ +.bug, .task {color:gray; font-size:9px} + +.cont-lt1 .side{width:220px} + diff --git a/trunk/module/project/js/common.js b/trunk/module/project/js/common.js new file mode 100644 index 0000000000..84e5bf3a9d --- /dev/null +++ b/trunk/module/project/js/common.js @@ -0,0 +1,93 @@ +function setWhite(acl) +{ + acl == 'custom' ? $('#whitelistBox').removeClass('hidden') : $('#whitelistBox').addClass('hidden'); +} + +function switchStatus(projectID, status) +{ + if(status) location.href = createLink('project', 'task', 'project=' + projectID + '&type=' + status); +} + +function switchGroup(projectID, groupBy) +{ + link = createLink('project', 'groupTask', 'project=' + projectID + '&groupBy=' + groupBy); + location.href=link; +} + +/** + * Convert a date string like 2011-11-11 to date object in js. + * + * @param string $date + * @access public + * @return date + */ +function convertStringToDate(dateString) +{ + dateString = dateString.split('-'); + dateString = dateString[1] + '/' + dateString[2] + '/' + dateString[0]; + + return Date.parse(dateString); +} + +/** + * Compute delta of two days. + * + * @param string $date1 + * @param string $date1 + * @access public + * @return int + */ +function computeDaysDelta(date1, date2) +{ + date1 = convertStringToDate(date1); + date2 = convertStringToDate(date2); + return (date2 - date1) / (1000 * 60 * 60 * 24) + 1; +} + +/** + * Compute work days. + * + * @access public + * @return void + */ +function computeWorkDays() +{ + beginDate = $('#begin').val(); + endDate = $('#end').val(); + if(beginDate && endDate) + { + $('#days').val(computeDaysDelta(beginDate, endDate)); + } + else if($('input[checked="true"]').val()) + { + computeEndDate(); + } +} + +/** + * Compute the end date for project. + * + * @param int $delta + * @access public + * @return void + */ +function computeEndDate(delta) +{ + beginDate = $('#begin').val(); + if(!beginDate) return; + + endDate = convertStringToDate(beginDate).addDays(parseInt(delta)); + endDate = endDate.toString('yyyy-M-dd'); + $('#end').val(endDate); + computeWorkDays(); +} + +/* Auto compute the work days. */ +$(function() +{ + $(".date").bind('dateSelected', function() + { + computeWorkDays(); + }) +}); + diff --git a/trunk/module/project/js/create.js b/trunk/module/project/js/create.js new file mode 100644 index 0000000000..f9eff2e5e7 --- /dev/null +++ b/trunk/module/project/js/create.js @@ -0,0 +1,16 @@ +function switchCopyProject(switcher) +{ + if($(switcher).attr('checked')) + { + $('#copyProjectBox').removeClass('hidden'); + } + else + { + $('#copyProjectBox').addClass('hidden'); + } +} + +function setCopyProject(projectID) +{ + location.href = createLink('project', 'create', 'projectID=0©ProjectID=' + projectID); +} diff --git a/trunk/module/project/js/dynamic.js b/trunk/module/project/js/dynamic.js new file mode 100755 index 0000000000..be82bb2190 --- /dev/null +++ b/trunk/module/project/js/dynamic.js @@ -0,0 +1,5 @@ +function changeUser(account, projectID) +{ + link = createLink('project', 'dynamic', 'projectID=' + projectID + '&type=account¶m=' + account); + location.href = link; +} diff --git a/trunk/module/project/js/importbug.js b/trunk/module/project/js/importbug.js new file mode 100644 index 0000000000..cdaccc8e08 --- /dev/null +++ b/trunk/module/project/js/importbug.js @@ -0,0 +1,4 @@ +$(function() +{ + $('#' + browseType + 'Tab').addClass('active'); +}); diff --git a/trunk/module/project/js/importtask.js b/trunk/module/project/js/importtask.js new file mode 100644 index 0000000000..7b76460b86 --- /dev/null +++ b/trunk/module/project/js/importtask.js @@ -0,0 +1,5 @@ +function reload(toProject, fromProject) +{ + link = createLink('project','importtask','toProject='+toProject + '&fromProject='+fromProject); + location.href = link; +} diff --git a/trunk/module/project/js/index.js b/trunk/module/project/js/index.js new file mode 100644 index 0000000000..742a3b7329 --- /dev/null +++ b/trunk/module/project/js/index.js @@ -0,0 +1,7 @@ +$().ready(function() +{ + $('.projectline').each(function() + { + $(this).sparkline('html', {height:'25px'}); + }) +}) diff --git a/trunk/module/project/js/story.js b/trunk/module/project/js/story.js new file mode 100644 index 0000000000..3eee9acf9b --- /dev/null +++ b/trunk/module/project/js/story.js @@ -0,0 +1,18 @@ +$(document).ready(function() +{ + $("a.iframe").colorbox({width:640, height:480, iframe:true, transition:'none'}); +}); + +/** + * Change form action. + * + * @param formName $formName + * @param actionName $actionName + * @param actionLink $actionLink + * @access public + * @return void + */ +function changeAction(formName, actionName, actionLink) +{ + $('#' + formName).attr('action', actionLink).submit(); +} diff --git a/trunk/module/project/js/task.js b/trunk/module/project/js/task.js new file mode 100644 index 0000000000..948121fb13 --- /dev/null +++ b/trunk/module/project/js/task.js @@ -0,0 +1,21 @@ +/* Browse by module. */ +function browseByModule() +{ + $('#querybox').addClass('hidden'); + $('#featurebar .active').removeClass('active'); + $('#bymoduleTab').addClass('active'); +} + +$(function() +{ + $('#' + browseType + 'Tab').addClass('active'); +}); + +/* Browse by project. */ +function browseByProject() +{ + $('#querybox').addClass('hidden'); + $('#byProjectTab').addClass('active'); + $('#featurebar .active').removeClass('active'); +} + diff --git a/trunk/module/project/lang/en.php b/trunk/module/project/lang/en.php new file mode 100644 index 0000000000..6995b0ab98 --- /dev/null +++ b/trunk/module/project/lang/en.php @@ -0,0 +1,190 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +/* Fields. */ +$lang->project->common = 'Project'; +$lang->project->id = 'ID'; +$lang->project->company = 'Company'; +$lang->project->fromproject = 'FromProject'; +$lang->project->iscat = 'Is category'; +$lang->project->type = 'Type'; +$lang->project->parent = 'Parent'; +$lang->project->name = 'Name'; +$lang->project->code = 'Code'; +$lang->project->begin = 'Begin'; +$lang->project->end = 'End'; +$lang->project->days = 'Workdays'; +$lang->project->day = 'day'; +$lang->project->status = 'Status'; +$lang->project->statge = 'Stage'; +$lang->project->pri = 'Priority'; +$lang->project->desc = 'Desc'; +$lang->project->goal = 'Goal'; +$lang->project->openedBy = 'Opened by'; +$lang->project->openedDate = 'Opened date'; +$lang->project->closedBy = 'Closed by'; +$lang->project->closedDate = 'Closed date'; +$lang->project->canceledBy = 'Canceled by'; +$lang->project->canceledDate = 'Canceled date'; +$lang->project->PO = 'Product owner'; +$lang->project->PM = 'Project manager'; +$lang->project->QM = 'Test manager'; +$lang->project->RM = 'Release manager'; +$lang->project->acl = 'Access limitation'; +$lang->project->teamname = 'Team name'; +$lang->project->order = 'Project order'; +$lang->project->products = 'Products'; +$lang->project->childProjects = 'Child projects'; +$lang->project->whitelist = 'Whitelist'; +$lang->project->totalEstimate = 'Est'; +$lang->project->totalConsumed = 'Done'; +$lang->project->totalLeft = 'Left'; +$lang->project->progess = 'Progess'; +$lang->project->viewBug = 'View bug'; +$lang->project->createTesttask= 'Create testtask'; +$lang->project->noProduct = 'No product'; +$lang->project->select = '--select project--'; + +$lang->project->endList[14] = 'Two Weeks'; +$lang->project->endList[31] = 'One Month'; +$lang->project->endList[62] = 'Two Months'; +$lang->project->endList[93] = 'Three Months'; +$lang->project->endList[186] = 'Half Of Year'; +$lang->project->endList[365] = 'One Year'; + +$lang->team->account = 'Account'; +$lang->team->role = 'Role'; +$lang->team->join = 'Join date'; +$lang->team->hours = 'Hour/Day'; +$lang->team->days = 'Workdays'; +$lang->team->totalHours = 'Total'; + +$lang->project->basicInfo = 'Basic info'; +$lang->project->otherInfo = 'Other info'; + +/* Lists. */ +$lang->project->statusList[''] = ''; +$lang->project->statusList['wait'] = 'Pending'; +$lang->project->statusList['doing'] = 'Progressing'; +$lang->project->statusList['done'] = 'Done'; + +$lang->project->aclList['open'] = 'Default(Having the priviledge of project module can visit this project)'; +$lang->project->aclList['private'] = 'Private(Only team members can visit)'; +$lang->project->aclList['custom'] = 'Whitelist(Team members and who belongs to the whitelist grups can visit)'; + +/* Methods.*/ +$lang->project->index = "Index"; +$lang->project->task = 'Task'; +$lang->project->groupTask = 'View task by group'; +$lang->project->story = 'Story'; +$lang->project->bug = 'Bug'; +$lang->project->dynamic = 'Dynamic'; +$lang->project->build = 'Build'; +$lang->project->testtask = 'Testtask'; +$lang->project->burn = 'Burndown chart'; +$lang->project->computeBurn = 'Update burndown'; +$lang->project->burnData = 'Burndown data'; +$lang->project->team = 'Team'; +$lang->project->doc = 'Doc'; +$lang->project->manageProducts = 'Link product'; +$lang->project->linkStory = 'Link story'; +$lang->project->view = "Info"; +$lang->project->create = "Add"; +$lang->project->copy = "Copy a project"; +$lang->project->delete = "Delete"; +$lang->project->browse = "Browse"; +$lang->project->edit = "Edit"; +$lang->project->manageMembers = 'Manage team members'; +$lang->project->unlinkMember = 'Remove member'; +$lang->project->unlinkStory = 'Remove story'; +$lang->project->importTask = 'Import task'; +$lang->project->importBug = 'Import bug'; +$lang->project->ajaxGetProducts = "API: get project's products"; + +/* Browse. */ +$lang->project->allTasks = 'All'; +$lang->project->assignedToMe = 'To me'; + +$lang->project->statusSelects[''] = 'More'; +$lang->project->statusSelects['finishedbyme'] = 'Mydone'; +$lang->project->statusSelects['wait'] = 'Pending'; +$lang->project->statusSelects['doing'] = 'Progressing'; +$lang->project->statusSelects['done'] = 'Done'; +$lang->project->statusSelects['closed'] = 'Closed'; +$lang->project->statusSelects['delayed'] = 'Delayed'; +$lang->project->statusSelects['needconfirm'] = 'Story changed'; +$lang->project->groups[''] = 'Group View'; +$lang->project->groups['story'] = 'Group by story'; +$lang->project->groups['status'] = 'Group by status'; +$lang->project->groups['pri'] = 'Group by priority'; +$lang->project->groups['openedby'] = 'Group by openedBy'; +$lang->project->groups['assignedTo'] = 'Group by assignedTo'; +$lang->project->groups['finishedby'] = 'Group by finishedBy'; +$lang->project->groups['closedby'] = 'Group by closedBy'; +$lang->project->groups['estimate'] = 'Group by estimate'; +$lang->project->groups['consumed'] = 'Group by consumed'; +$lang->project->groups['left'] = 'Group by left'; +$lang->project->groups['type'] = 'Group by type'; +$lang->project->groups['deadline'] = 'Group by deadline'; + +$lang->project->moduleTask = 'Module'; +$lang->project->byQuery = 'Search'; + +/* Browse tabs. */ +$lang->project->allProject = 'All projects'; +$lang->project->aboveAllProduct = 'Above all products'; +$lang->project->aboveAllProject = 'Above all projects'; + +/* Notcie. */ +$lang->project->selectProject = "Select project"; +$lang->project->beginAndEnd = 'Begin and end'; +$lang->project->lblStats = 'Stats'; +$lang->project->stats = 'Total work hours is 『%s』hours,
      Total estimate is『%s』hours,
      Total confused is『%s』hours
      Total left is『%s』hours'; +$lang->project->oneLineStats = "Project『%s』, code is『%s』, products is『%s』,begin from『%s』to 『%s』,total estimate『%s』hours,consumed『%s』hours,left『%s』hours."; +$lang->project->taskSummary = "Total tasks shown: %s. Pending: %s. In progress: %s. Estimate: %s hrs. Consumed: %s hrs. Hours left: %s."; +$lang->project->memberHours = "%s has %s workhours, "; +$lang->project->groupSummary = "%s tasks in this group, wait:%s, doing:%s, estimate %s, consumed %s, left %s hours."; +$lang->project->wbs = "WBS"; +$lang->project->largeBurnChart = 'View large'; +$lang->project->howToUpdateBurn = "How?"; +$lang->project->whyNoStories = "There no active stories to added to this project. Please check the linked product."; +$lang->project->doneProjects = 'Done'; +$lang->project->unDoneProjects = 'Undone'; + +/* Confirm. */ +$lang->project->confirmDelete = 'Are you sure to delete project [%s]?'; +$lang->project->confirmUnlinkMember = 'Are you sure to remove this user from this project?'; +$lang->project->confirmUnlinkStory = 'Are you sure to remove the story from this project?'; +$lang->project->errorNoLinkedProducts = 'There is no linked products, go to the link page.'; +$lang->project->accessDenied = 'Access to this project denied.'; +$lang->project->tips = 'Tips'; +$lang->project->afterInfo = 'Successful and you can do:'; +$lang->project->setTeam = 'Set team'; +$lang->project->linkStory = 'Link story'; +$lang->project->createTask = 'Create task'; +$lang->project->goback = 'Go back(Automatically after 5 seconds)'; + +/* Report. */ +$lang->project->charts->burn->graph->caption = "Burndown chart"; +$lang->project->charts->burn->graph->xAxisName = "Date"; +$lang->project->charts->burn->graph->yAxisName = "HOUR"; +$lang->project->charts->burn->graph->baseFontSize = 12; +$lang->project->charts->burn->graph->formatNumber = 0; +$lang->project->charts->burn->graph->animation = 0; +$lang->project->charts->burn->graph->rotateNames = 1; +$lang->project->charts->burn->graph->showValues = 0; + +$lang->project->placeholder->code = 'Project code'; + +$lang->project->selectGroup->doing = '(Doing)'; +$lang->project->selectGroup->done = '(Done)'; + +$lang->project->projectTasks = 'ProjectTasks'; diff --git a/trunk/module/project/lang/zh-cn.php b/trunk/module/project/lang/zh-cn.php new file mode 100644 index 0000000000..acf92a0b67 --- /dev/null +++ b/trunk/module/project/lang/zh-cn.php @@ -0,0 +1,190 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +/* 字段列表。*/ +$lang->project->common = '项目视图'; +$lang->project->id = '项目编号'; +$lang->project->company = '所属公司'; +$lang->project->fromproject = '所属项目'; +$lang->project->iscat = '作为目录'; +$lang->project->type = '项目类型'; +$lang->project->parent = '上级项目'; +$lang->project->name = '项目名称'; +$lang->project->code = '项目代号'; +$lang->project->begin = '开始日期'; +$lang->project->end = '结束日期'; +$lang->project->days = '可用工作日'; +$lang->project->day = '天'; +$lang->project->status = '项目状态'; +$lang->project->statge = '所处阶段'; +$lang->project->pri = '优先级'; +$lang->project->desc = '项目描述'; +$lang->project->goal = '项目目标'; +$lang->project->openedBy = '由谁创建'; +$lang->project->openedDate = '创建日期'; +$lang->project->closedBy = '由谁关闭'; +$lang->project->closedDate = '关闭日期'; +$lang->project->canceledBy = '由谁取消'; +$lang->project->canceledDate = '取消日期'; +$lang->project->PO = '产品负责人'; +$lang->project->PM = '项目负责人'; +$lang->project->QM = '测试负责人'; +$lang->project->RM = '发布负责人'; +$lang->project->acl = '访问控制'; +$lang->project->teamname = '团队名称'; +$lang->project->order = '项目排序'; +$lang->project->products = '相关产品'; +$lang->project->childProjects = '子项目'; +$lang->project->whitelist = '分组白名单'; +$lang->project->totalEstimate = '总预计'; +$lang->project->totalConsumed = '总消耗'; +$lang->project->totalLeft = '总剩余'; +$lang->project->progess = '进度'; +$lang->project->viewBug = '查看bug'; +$lang->project->createTesttask= '提交测试'; +$lang->project->noProduct = '无产品项目'; +$lang->project->select = '--请选择项目--'; + +$lang->project->endList[14] = '两星期'; +$lang->project->endList[31] = '一个月'; +$lang->project->endList[62] = '两个月'; +$lang->project->endList[93] = '三个月'; +$lang->project->endList[186] = '半年'; +$lang->project->endList[365] = '一年'; + +$lang->team->account = '用户'; +$lang->team->role = '角色'; +$lang->team->join = '加盟日'; +$lang->team->hours = '可用工时/天'; +$lang->team->days = '可用工日'; +$lang->team->totalHours = '总计'; + +$lang->project->basicInfo = '基本信息'; +$lang->project->otherInfo = '其他信息'; + +/* 字段取值列表。*/ +$lang->project->statusList[''] = ''; +$lang->project->statusList['wait'] = '未开始'; +$lang->project->statusList['doing'] = '进行中'; +$lang->project->statusList['done'] = '已完成'; + +$lang->project->aclList['open'] = '默认设置(有项目视图权限,即可访问)'; +$lang->project->aclList['private'] = '私有项目(只有项目团队成员才能访问)'; +$lang->project->aclList['custom'] = '自定义白名单(团队成员和白名单的成员可以访问)'; + +/* 方法列表。*/ +$lang->project->index = "项目首页"; +$lang->project->task = '任务列表'; +$lang->project->groupTask = '分组浏览任务'; +$lang->project->story = '需求列表'; +$lang->project->bug = 'Bug列表'; +$lang->project->dynamic = '动态'; +$lang->project->build = '版本列表'; +$lang->project->testtask = '测试申请'; +$lang->project->burn = '燃尽图'; +$lang->project->computeBurn = '更新燃尽图'; +$lang->project->burnData = '燃尽图数据'; +$lang->project->team = '团队成员'; +$lang->project->doc = '文档列表'; +$lang->project->manageProducts = '关联产品'; +$lang->project->linkStory = '关联需求'; +$lang->project->view = "基本信息"; +$lang->project->create = "添加项目"; +$lang->project->copy = "复制项目"; +$lang->project->delete = "删除项目"; +$lang->project->browse = "浏览项目"; +$lang->project->edit = "编辑项目"; +$lang->project->manageMembers = '团队管理'; +$lang->project->unlinkMember = '移除成员'; +$lang->project->unlinkStory = '移除需求'; +$lang->project->importTask = '导入任务'; +$lang->project->importBug = '导入Bug'; +$lang->project->ajaxGetProducts = '接口:获得项目产品列表'; + +/* 分组浏览。*/ +$lang->project->allTasks = '所有'; +$lang->project->assignedToMe = '指派给我'; + +$lang->project->statusSelects[''] = '更多'; +$lang->project->statusSelects['finishedbyme'] = '我完成'; +$lang->project->statusSelects['wait'] = '未开始'; +$lang->project->statusSelects['doing'] = '进行中'; +$lang->project->statusSelects['done'] = '已完成'; +$lang->project->statusSelects['closed'] = '已关闭'; +$lang->project->statusSelects['delayed'] = '已延期'; +$lang->project->statusSelects['needconfirm'] = '需求变动'; +$lang->project->groups[''] = '分组查看'; +$lang->project->groups['story'] = '需求分组'; +$lang->project->groups['status'] = '状态分组'; +$lang->project->groups['pri'] = '优先级分组'; +$lang->project->groups['openedby'] = '创建者分组'; +$lang->project->groups['assignedTo'] = '指派给分组'; +$lang->project->groups['finishedby'] = '完成者分组'; +$lang->project->groups['closedby'] = '关闭者分组'; +$lang->project->groups['estimate'] = '预计分组'; +$lang->project->groups['consumed'] = '已消耗分组'; +$lang->project->groups['left'] = '剩余分组'; +$lang->project->groups['type'] = '类型分组'; +$lang->project->groups['deadline'] = '截止分组'; + +$lang->project->moduleTask = '按模块'; +$lang->project->byQuery = '搜索'; + +/* 查询条件列表。*/ +$lang->project->allProject = '所有项目'; +$lang->project->aboveAllProduct = '以上所有产品'; +$lang->project->aboveAllProject = '以上所有项目'; + +/* 页面提示。*/ +$lang->project->selectProject = "请选择项目"; +$lang->project->beginAndEnd = '起止时间'; +$lang->project->lblStats = '工时统计'; +$lang->project->stats = '可用工时%s工时
      总共预计%s工时
      已经消耗%s工时
      预计剩余%s工时'; +$lang->project->oneLineStats = "项目%s, 代号为%s, 相关产品为%s%s开始,%s结束,总预计%s工时,已消耗%s工时,预计剩余%s工时。"; +$lang->project->taskSummary = "本页共 %s 个任务,未开始%s,进行中%s,总预计%s工时,已消耗%s工时,剩余%s工时。"; +$lang->project->memberHours = "%s共有 %s 个可用工时,"; +$lang->project->groupSummary = "本组共 %s 个任务,未开始%s,进行中%s,总预计%s工时,已消耗%s工时,剩余%s工时。"; +$lang->project->wbs = "分解任务"; +$lang->project->largeBurnChart = '点击查看大图'; +$lang->project->howToUpdateBurn = "如何更新?"; +$lang->project->whyNoStories = "看起来没有需求可以关联。请检查下项目关联的产品中有没有需求,而且要确保它们已经审核通过。"; +$lang->project->doneProjects = '已结束'; +$lang->project->unDoneProjects = '未结束'; + +/* 交互提示。*/ +$lang->project->confirmDelete = '您确定删除项目[%s]吗?'; +$lang->project->confirmUnlinkMember = '您确定从该项目中移除该用户吗?'; +$lang->project->confirmUnlinkStory = '您确定从该项目中移除该需求吗?'; +$lang->project->errorNoLinkedProducts = '该项目没有关联的产品,系统将转到产品关联页面'; +$lang->project->accessDenied = '您无权访问该项目!'; +$lang->project->tips = '提示'; +$lang->project->afterInfo = '项目添加成功,您现在可以进行以下操作:'; +$lang->project->setTeam = '设置团队'; +$lang->project->linkStory = '关联需求'; +$lang->project->createTask = '添加任务'; +$lang->project->goback = '返回项目首页(5秒后将自动跳转)'; + +/* 统计。*/ +$lang->project->charts->burn->graph->caption = "燃尽图"; +$lang->project->charts->burn->graph->xAxisName = "日期"; +$lang->project->charts->burn->graph->yAxisName = "HOUR"; +$lang->project->charts->burn->graph->baseFontSize = 12; +$lang->project->charts->burn->graph->formatNumber = 0; +$lang->project->charts->burn->graph->animation = 0; +$lang->project->charts->burn->graph->rotateNames = 1; +$lang->project->charts->burn->graph->showValues = 0; + +$lang->project->placeholder->code = '团队内部的简称'; + +$lang->project->selectGroup->doing = '(进行中)'; +$lang->project->selectGroup->done = '(已结束)'; + +$lang->project->projectTasks = '按项目'; diff --git a/trunk/module/project/lang/zh-tw.php b/trunk/module/project/lang/zh-tw.php new file mode 100644 index 0000000000..28e8193596 --- /dev/null +++ b/trunk/module/project/lang/zh-tw.php @@ -0,0 +1,190 @@ + + * @package project + * @version $Id: zh-tw.php 3481 2012-09-02 05:53:18Z wwccss $ + * @link http://www.zentao.net + */ +/* 欄位列表。*/ +$lang->project->common = '項目視圖'; +$lang->project->id = '項目編號'; +$lang->project->company = '所屬公司'; +$lang->project->fromproject = '所屬項目'; +$lang->project->iscat = '作為目錄'; +$lang->project->type = '項目類型'; +$lang->project->parent = '上級項目'; +$lang->project->name = '項目名稱'; +$lang->project->code = '項目代號'; +$lang->project->begin = '開始日期'; +$lang->project->end = '結束日期'; +$lang->project->days = '可用工作日'; +$lang->project->day = '天'; +$lang->project->status = '項目狀態'; +$lang->project->statge = '所處階段'; +$lang->project->pri = '優先順序'; +$lang->project->desc = '項目描述'; +$lang->project->goal = '項目目標'; +$lang->project->openedBy = '由誰創建'; +$lang->project->openedDate = '創建日期'; +$lang->project->closedBy = '由誰關閉'; +$lang->project->closedDate = '關閉日期'; +$lang->project->canceledBy = '由誰取消'; +$lang->project->canceledDate = '取消日期'; +$lang->project->PO = '產品負責人'; +$lang->project->PM = '項目負責人'; +$lang->project->QM = '測試負責人'; +$lang->project->RM = '發佈負責人'; +$lang->project->acl = '訪問控制'; +$lang->project->teamname = '團隊名稱'; +$lang->project->order = '項目排序'; +$lang->project->products = '相關產品'; +$lang->project->childProjects = '子項目'; +$lang->project->whitelist = '分組白名單'; +$lang->project->totalEstimate = '總預計'; +$lang->project->totalConsumed = '總消耗'; +$lang->project->totalLeft = '總剩餘'; +$lang->project->progess = '進度'; +$lang->project->viewBug = '查看bug'; +$lang->project->createTesttask= '提交測試'; +$lang->project->noProduct = '無產品項目'; +$lang->project->select = '--請選擇項目--'; + +$lang->project->endList[14] = '兩星期'; +$lang->project->endList[31] = '一個月'; +$lang->project->endList[62] = '兩個月'; +$lang->project->endList[93] = '三個月'; +$lang->project->endList[186] = '半年'; +$lang->project->endList[365] = '一年'; + +$lang->team->account = '用戶'; +$lang->team->role = '角色'; +$lang->team->join = '加盟日'; +$lang->team->hours = '可用工時/天'; +$lang->team->days = '可用工日'; +$lang->team->totalHours = '總計'; + +$lang->project->basicInfo = '基本信息'; +$lang->project->otherInfo = '其他信息'; + +/* 欄位取值列表。*/ +$lang->project->statusList[''] = ''; +$lang->project->statusList['wait'] = '未開始'; +$lang->project->statusList['doing'] = '進行中'; +$lang->project->statusList['done'] = '已完成'; + +$lang->project->aclList['open'] = '預設設置(有項目視圖權限,即可訪問)'; +$lang->project->aclList['private'] = '私有項目(只有項目團隊成員才能訪問)'; +$lang->project->aclList['custom'] = '自定義白名單(團隊成員和白名單的成員可以訪問)'; + +/* 方法列表。*/ +$lang->project->index = "項目首頁"; +$lang->project->task = '任務列表'; +$lang->project->groupTask = '分組瀏覽任務'; +$lang->project->story = '需求列表'; +$lang->project->bug = 'Bug列表'; +$lang->project->dynamic = '動態'; +$lang->project->build = '版本列表'; +$lang->project->testtask = '測試申請'; +$lang->project->burn = '燃盡圖'; +$lang->project->computeBurn = '更新燃盡圖'; +$lang->project->burnData = '燃盡圖數據'; +$lang->project->team = '團隊成員'; +$lang->project->doc = '文檔列表'; +$lang->project->manageProducts = '關聯產品'; +$lang->project->linkStory = '關聯需求'; +$lang->project->view = "基本信息"; +$lang->project->create = "添加項目"; +$lang->project->copy = "複製項目"; +$lang->project->delete = "刪除項目"; +$lang->project->browse = "瀏覽項目"; +$lang->project->edit = "編輯項目"; +$lang->project->manageMembers = '團隊管理'; +$lang->project->unlinkMember = '移除成員'; +$lang->project->unlinkStory = '移除需求'; +$lang->project->importTask = '導入任務'; +$lang->project->importBug = '導入Bug'; +$lang->project->ajaxGetProducts = '介面:獲得項目產品列表'; + +/* 分組瀏覽。*/ +$lang->project->allTasks = '所有'; +$lang->project->assignedToMe = '指派給我'; + +$lang->project->statusSelects[''] = '更多'; +$lang->project->statusSelects['finishedbyme'] = '我完成'; +$lang->project->statusSelects['wait'] = '未開始'; +$lang->project->statusSelects['doing'] = '進行中'; +$lang->project->statusSelects['done'] = '已完成'; +$lang->project->statusSelects['closed'] = '已關閉'; +$lang->project->statusSelects['delayed'] = '已延期'; +$lang->project->statusSelects['needconfirm'] = '需求變動'; +$lang->project->groups[''] = '分組查看'; +$lang->project->groups['story'] = '需求分組'; +$lang->project->groups['status'] = '狀態分組'; +$lang->project->groups['pri'] = '優先順序分組'; +$lang->project->groups['openedby'] = '創建者分組'; +$lang->project->groups['assignedTo'] = '指派給分組'; +$lang->project->groups['finishedby'] = '完成者分組'; +$lang->project->groups['closedby'] = '關閉者分組'; +$lang->project->groups['estimate'] = '預計分組'; +$lang->project->groups['consumed'] = '已消耗分組'; +$lang->project->groups['left'] = '剩餘分組'; +$lang->project->groups['type'] = '類型分組'; +$lang->project->groups['deadline'] = '截止分組'; + +$lang->project->moduleTask = '按模組'; +$lang->project->byQuery = '搜索'; + +/* 查詢條件列表。*/ +$lang->project->allProject = '所有項目'; +$lang->project->aboveAllProduct = '以上所有產品'; +$lang->project->aboveAllProject = '以上所有項目'; + +/* 頁面提示。*/ +$lang->project->selectProject = "請選擇項目"; +$lang->project->beginAndEnd = '起止時間'; +$lang->project->lblStats = '工時統計'; +$lang->project->stats = '可用工時%s工時
      總共預計%s工時
      已經消耗%s工時
      預計剩餘%s工時'; +$lang->project->oneLineStats = "項目%s, 代號為%s, 相關產品為%s%s開始,%s結束,總預計%s工時,已消耗%s工時,預計剩餘%s工時。"; +$lang->project->taskSummary = "本頁共 %s 個任務,未開始%s,進行中%s,總預計%s工時,已消耗%s工時,剩餘%s工時。"; +$lang->project->memberHours = "%s共有 %s 個可用工時,"; +$lang->project->groupSummary = "本組共 %s 個任務,未開始%s,進行中%s,總預計%s工時,已消耗%s工時,剩餘%s工時。"; +$lang->project->wbs = "分解任務"; +$lang->project->largeBurnChart = '點擊查看大圖'; +$lang->project->howToUpdateBurn = "如何更新?"; +$lang->project->whyNoStories = "看起來沒有需求可以關聯。請檢查下項目關聯的產品中有沒有需求,而且要確保它們已經審核通過。"; +$lang->project->doneProjects = '已結束'; +$lang->project->unDoneProjects = '未結束'; + +/* 交互提示。*/ +$lang->project->confirmDelete = '您確定刪除項目[%s]嗎?'; +$lang->project->confirmUnlinkMember = '您確定從該項目中移除該用戶嗎?'; +$lang->project->confirmUnlinkStory = '您確定從該項目中移除該需求嗎?'; +$lang->project->errorNoLinkedProducts = '該項目沒有關聯的產品,系統將轉到產品關聯頁面'; +$lang->project->accessDenied = '您無權訪問該項目!'; +$lang->project->tips = '提示'; +$lang->project->afterInfo = '項目添加成功,您現在可以進行以下操作:'; +$lang->project->setTeam = '設置團隊'; +$lang->project->linkStory = '關聯需求'; +$lang->project->createTask = '添加任務'; +$lang->project->goback = '返回項目首頁(5秒後將自動跳轉)'; + +/* 統計。*/ +$lang->project->charts->burn->graph->caption = "燃盡圖"; +$lang->project->charts->burn->graph->xAxisName = "日期"; +$lang->project->charts->burn->graph->yAxisName = "HOUR"; +$lang->project->charts->burn->graph->baseFontSize = 12; +$lang->project->charts->burn->graph->formatNumber = 0; +$lang->project->charts->burn->graph->animation = 0; +$lang->project->charts->burn->graph->rotateNames = 1; +$lang->project->charts->burn->graph->showValues = 0; + +$lang->project->placeholder->code = '團隊內部的簡稱'; + +$lang->project->selectGroup->doing = '(進行中)'; +$lang->project->selectGroup->done = '(已結束)'; + +$lang->project->projectTasks = '按項目'; diff --git a/trunk/module/project/model.php b/trunk/module/project/model.php new file mode 100644 index 0000000000..c6fbb1dc74 --- /dev/null +++ b/trunk/module/project/model.php @@ -0,0 +1,1161 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> +app->user->account . ','; + if(strpos($this->app->company->admins, $account) !== false) return true; + + /* If project is open, return true. */ + if($project->acl == 'open') return true; + + /* Get all teams of all projects and group by projects, save it as static. */ + static $teams; + if(empty($teams)) $teams = $this->dao->select('project, account')->from(TABLE_TEAM)->fetchGroup('project', 'account'); + $currentTeam = isset($teams[$project->id]) ? $teams[$project->id] : array(); + + /* If project is private, only members can access. */ + if($project->acl == 'private') + { + return isset($currentTeam[$this->app->user->account]); + } + + /* Project's acl is custom, check the groups. */ + if($project->acl == 'custom') + { + if(isset($currentTeam[$this->app->user->account])) return true; + $userGroups = $this->app->user->groups; + $projectGroups = explode(',', $project->whitelist); + foreach($userGroups as $groupID) + { + if(in_array($groupID, $projectGroups)) return true; + } + return false; + } + } + + /** + * Set menu. + * + * @param array $projects + * @param int $projectID + * @access public + * @return void + */ + public function setMenu($projects, $projectID) + { + /* Check the privilege. */ + if($projects and !isset($projects[$projectID]) and !$this->checkPriv($this->getById($projectID))) + { + echo(js::alert($this->lang->project->accessDenied)); + die(js::locate('back')); + } + + $moduleName = $this->app->getModuleName(); + $methodName = $this->app->getMethodName(); + $selectHtml = $this->select($projects, $projectID, $moduleName, $methodName); + foreach($this->lang->project->menu as $key => $menu) + { + $replace = $key == 'list' ? $selectHtml : $projectID; + common::setMenuVars($this->lang->project->menu, $key, $replace); + } + } + + /** + * Create the select code of projects. + * + * @param array $projects + * @param int $projectID + * @param string $currentModule + * @param string $currentMethod + * @access public + * @return string + */ + public function select($projects, $projectID, $currentModule, $currentMethod) + { + $projectMode = $this->cookie->projectMode ? $this->cookie->projectMode : 'all'; + $products = $this->loadModel('product')->getPairs('nocode'); + $productGroup = $this->getProductGroupList(); + $selectGroup = array(); + foreach($productGroup as $projects) + { + foreach($projects as $project) + { + if($projectMode == 'noclosed' and $project->status == 'done') continue; + + $status = $project->status != 'done' ? $this->lang->project->selectGroup->doing : ''; + if($project->product) + { + if(isset($products[$project->product])) + { + $selectGroup[$products[$project->product]][$project->id] = $project->name . $status; + } + } + else + { + $selectGroup[$this->lang->project->noProduct][$project->id] = $project->name . $status; + } + } + } + + /* See product's model method:select. */ + $switchCode = "switchProject($('#projectID').val(), '$currentModule', '$currentMethod');"; + $onchange = "onchange=\"$switchCode\""; + $onkeypress = "onkeypress=\"eventKeyCode=event.keyCode; if(eventKeyCode == 13) $switchCode\""; + $onclick = "onclick=\"eventKeyCode = 13; $switchCode\""; + $selectHtml = html::selectGroup('projectID', $selectGroup, $projectID, "tabindex=2 $onchange $onkeypress"); + return $selectHtml; + } + + /** + * Get project tree menu. + * + * @access public + * @return void + */ + public function tree() + { + $products = $this->loadModel('product')->getPairs('nocode'); + $productGroup = $this->getProductGroupList(); + $projectTree = "
        "; + foreach($productGroup as $productID => $projects) + { + if(!isset($products[$productID]) and $productID != '') continue; + $productName = isset($products[$productID]) ? $products[$productID] : $this->lang->project->noProduct; + $projectTree .= "
      • $productName
          "; + + foreach($projects as $project) + { + if($project->status != 'done') + { + $projectTree .= "
        • " . html::a(inlink('task', "projectID=$project->id"), $project->name, '', "id='project$project->id'") . "
        • "; + } + } + + + $hasDone = false; + foreach($projects as $project) + { + if($project->status == 'done') + { + $hasDone = true; + break; + } + } + if($hasDone) + { + $projectTree .= "
        • {$this->lang->project->selectGroup->done}
            "; + foreach($projects as $project) + { + if($project->status == 'done') + { + $projectTree .= "
          • " . html::a(inlink('task', "projectID=$project->id"), $project->name, '', "id='project$project->id'") . "
          • "; + } + } + $projectTree .= "
        • "; + } + + $projectTree .= "
      • "; + } + + $projectTree .= "
      "; + + return $projectTree; + } + + /** + * Save the project id user last visited to session. + * + * @param int $projectID + * @param array $projects + * @access public + * @return int + */ + public function saveState($projectID, $projects) + { + if($projectID > 0) $this->session->set('project', (int)$projectID); + if($projectID == 0 and $this->cookie->lastProject) $this->session->set('project', (int)$this->cookie->lastProject); + if($projectID == 0 and $this->session->project == '') $this->session->set('project', $projects[0]); + if(!in_array($this->session->project, $projects)) $this->session->set('project', $projects[0]); + return $this->session->project; + } + + /** + * Save order + * + * @access public + * @return void + */ + public function saveOrder() + { + foreach($_POST as $projectID => $order) + { + $this->dao->update(TABLE_PROJECT)->set('`order`')->eq($order)->where('id')->eq($projectID)->exec(); + } + } + + /** + * Create a project. + * + * @access public + * @return void + */ + public function create($copyProjectID = '') + { + $this->lang->project->team = $this->lang->project->teamname; + $project = fixer::input('post') + ->stripTags('name, code, team') + ->setIF($this->post->acl != 'custom', 'whitelist', '') + ->join('whitelist', ',') + ->remove('products, workDays, delta') + ->get(); + $this->dao->insert(TABLE_PROJECT)->data($project) + ->autoCheck($skipFields = 'begin,end') + ->batchcheck($this->config->project->create->requiredFields, 'notempty') + ->checkIF($project->begin != '', 'begin', 'date') + ->checkIF($project->end != '', 'end', 'date') + ->checkIF($project->end != '', 'end', 'gt', $project->begin) + ->check('name', 'unique') + ->check('code', 'unique') + ->exec(); + + /* Add the creater to the team. */ + if(!dao::isError()) + { + $projectID = $this->dao->lastInsertId(); + $today = helper::today(); + $creatorExists = false; + + /* Copy team of project. */ + if($copyProjectID != '') + { + $members = $this->dao->select('*')->from(TABLE_TEAM)->where('project')->eq($copyProjectID)->fetchAll(); + foreach($members as $member) + { + $member->project = $projectID; + $member->join = $today; + $this->dao->insert(TABLE_TEAM)->data($member)->exec(); + if($member->account == $this->app->user->account) $creatorExists = true; + } + } + + /* Add the creator to team. */ + if($copyProjectID == '' or !$creatorExists) + { + $member->project = $projectID; + $member->account = $this->app->user->account; + $member->join = $today; + $member->days = $project->days; + $member->hours = $this->config->project->defaultWorkhours; + $this->dao->insert(TABLE_TEAM)->data($member)->exec(); + } + + return $projectID; + } + } + + /** + * Update a project. + * + * @param int $projectID + * @access public + * @return array + */ + public function update($projectID) + { + $oldProject = $this->getById($projectID); + $team = $this->getTeamMemberPairs($projectID); + $this->lang->project->team = $this->lang->project->teamname; + $projectID = (int)$projectID; + $project = fixer::input('post') + ->stripTags('name, code, team') + ->setIF($this->post->begin == '0000-00-00', 'begin', '') + ->setIF($this->post->end == '0000-00-00', 'end', '') + ->setIF($this->post->acl != 'custom', 'whitelist', '') + ->join('whitelist', ',') + ->remove('products') + ->get(); + $this->dao->update(TABLE_PROJECT)->data($project) + ->autoCheck($skipFields = 'begin,end') + ->batchcheck($this->config->project->edit->requiredFields, 'notempty') + ->checkIF($project->begin != '', 'begin', 'date') + ->checkIF($project->end != '', 'end', 'date') + ->checkIF($project->end != '', 'end', 'gt', $project->begin) + ->check('name', 'unique', "id!=$projectID") + ->check('code', 'unique', "id!=$projectID") + ->where('id')->eq($projectID) + ->limit(1) + ->exec(); + foreach($project as $fieldName => $value) + { + if($fieldName == 'PO' or $fieldName == 'PM' or $fieldName == 'QM' or $fieldName == 'RM' ) + { + if(!empty($value) and !isset($team[$value])) + { + $member->project = (int)$projectID; + $member->account = $value; + $member->join = helper::today(); + $member->role = $fieldName; + $member->days = $project->days; + $member->hours = $this->config->project->defaultWorkhours; + $this->dao->insert(TABLE_TEAM)->data($member)->exec(); + } + } + } + if(!dao::isError()) return common::createChanges($oldProject, $project); + } + + /** + * Get project pairs. + * + * @param string $mode all|noclosed or empty + * @access public + * @return array + */ + public function getPairs($mode = '') + { + $orderBy = !empty($this->config->project->orderBy) ? $this->config->project->orderBy : 'isDone, `order`, status'; + $mode .= $this->cookie->projectMode; + /* Order by status's content whether or not done */ + $projects = $this->dao->select('*, IF(INSTR(" done", status) < 2, 0, 1) AS isDone')->from(TABLE_PROJECT) + ->where('iscat')->eq(0) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy) + ->fetchAll(); + $pairs = array(); + foreach($projects as $project) + { + if(strpos($mode, 'noclosed') !== false and $project->status == 'done') continue; + if($this->checkPriv($project)) + { + if(strpos($mode, 'nocode') === false and $project->code) + { + $firstChar = strtoupper(substr($project->code, 0, 1)); + if(ord($firstChar) < 127) $project->name = $firstChar . ':' . $project->name; + } + $pairs[$project->id] = $project->name; + } + } + + /* If the pairs is empty, to make sure there's an project in the pairs. */ + if(empty($pairs) and isset($projects[0]) and $this->checkPriv($projects[0])) + { + $firstProject = $projects[0]; + $pairs[$firstProject->id] = $firstProject->name; + } + return $pairs; + } + + /** + * Get project lists. + * + * @param string $status all|undone|wait|running + * @param int $limit + * @param int $productID + * @access public + * @return array + */ + public function getList($status = 'all', $limit = 0, $productID = 0) + { + if($productID != 0) + { + return $this->dao->select('t2.*') + ->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.product')->eq($productID) + ->andWhere('t2.deleted')->eq(0) + ->andWhere('t2.iscat')->eq(0) + ->beginIF($status == 'undone')->andWhere('t2.status')->ne('done')->fi() + ->beginIF($status != 'all' and $status != 'undone')->andWhere('status')->in($status)->fi() + ->orderBy('`order`, status') + ->beginIF($limit)->limit($limit)->fi() + ->fetchAll('id'); + } + else + { + return $this->dao->select('*, IF(INSTR(" done", status) < 2, 0, 1) AS isDone')->from(TABLE_PROJECT)->where('iscat')->eq(0) + ->beginIF($status == 'undone')->andWhere('status')->ne('done')->fi() + ->beginIF($status != 'all' and $status != 'undone')->andWhere('status')->in($status)->fi() + ->andWhere('deleted')->eq(0) + ->orderBy($this->config->project->orderBy) + ->beginIF($limit)->limit($limit)->fi() + ->fetchAll('id'); + } + } + + /** + * Get projects lists grouped by product. + * + * @access public + * @return array + */ + public function getProductGroupList() + { + $list = $this->dao->select('t1.id, t1.name,t1.status, t2.product')->from(TABLE_PROJECT)->alias('t1') + ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.project') + ->where('t1.deleted')->eq(0) + ->orderBy('t1.order') + ->fetchGroup('product'); + + $projects = $this->getList(); + + foreach($list as $id => $product) + { + foreach($product as $ID => $project) + { + if(!$this->checkPriv($projects[$project->id])) + { + unset($list[$id][$ID]); + } + } + } + + return $list; + } + + /** + * Get project stats. + * + * @param string $status + * @access public + * @return array + */ + public function getProjectStats($status = 'undone', $productID = 0, $itemCounts = 30) + { + /* Init vars. */ + $projects = $this->getList($status, 0, $productID); + $projectKeys = array_keys($projects); + $stats = array(); + $hours = array(); + $emptyHour = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0); + + /* Get all tasks and compute totalEstimate, totalConsumed, totalLeft, progress according to them. */ + $tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason') + ->from(TABLE_TASK) + ->where('project')->in($projectKeys) + ->andWhere('deleted')->eq(0) + ->fetchGroup('project', 'id'); + + /* Compute totalEstimate, totalConsumed, totalLeft. */ + foreach($tasks as $projectID => $projectTasks) + { + $hour = (object)$emptyHour; + foreach($projectTasks as $task) + { + $hour->totalEstimate += $task->estimate; + $hour->totalConsumed += $task->consumed; + $hour->totalLeft += ($task->status != 'cancel' and $task->closedReason != 'cancel') ? $task->left : 0; + } + $hours[$projectID] = $hour; + } + + /* Compute totalReal and progress. */ + foreach($hours as $hour) + { + $hour->totalEstimate = round($hour->totalEstimate, 1) ; + $hour->totalConsumed = round($hour->totalConsumed, 1); + $hour->totalLeft = round($hour->totalLeft, 1); + $hour->totalReal = $hour->totalConsumed + $hour->totalLeft; + $hour->progress = $hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 3) * 100 : 0; + } + + /* Get burndown charts datas. */ + $burns = $this->dao->select('project, date AS name, `left` AS value') + ->from(TABLE_BURN) + ->where('project')->in($projectKeys) + ->orderBy('date desc') + ->fetchGroup('project', 'name'); + + foreach($burns as $projectID => $projectBurns) + { + /* If projectBurns > $itemCounts, split it, else call processBurnData() to pad burns. */ + $begin = $projects[$projectID]->begin; + $end = $projects[$projectID]->end; + if(count($projectBurns) >= $itemCounts) $projectBurns = array_slice($projectBurns, 0, $itemCounts); + if(count($projectBurns) < $itemCounts) $projectBurns = $this->processBurnData($projectBurns, $itemCounts, $begin, $end); + + /* Shorter names. */ + foreach($projectBurns as $projectBurn) + { + $projectBurn->name = substr($projectBurn->name, 5); + unset($projectBurn->project); + } + + $burns[$projectID] = $projectBurns; + } + + /* Process projects. */ + foreach($projects as $key => $project) + { + if(!$this->checkPriv($project)) + { + unset($projects[$key]); + continue; + } + + // Process the end time. + $project->end = date(DT_DATE4, strtotime($project->end)); + + /* Process the burns. */ + $project->burns = array(); + $burnData = isset($burns[$project->id]) ? $burns[$project->id] : array(); + foreach($burnData as $data) $project->burns[] = $data->value; + + /* Process the hours. */ + $project->hours = isset($hours[$project->id]) ? $hours[$project->id] : (object)$emptyHour; + + $stats[] = $project; + } + + return $stats; + } + + /** + * Get project by id. + * + * @param int $projectID + * @param bool $setImgSize + * @access public + * @return void + */ + public function getById($projectID, $setImgSize = false) + { + $project = $this->dao->findById((int)$projectID)->from(TABLE_PROJECT)->fetch(); + if(!$project) return false; + + $total = $this->dao->select(' + SUM(estimate) AS totalEstimate, + SUM(consumed) AS totalConsumed, + SUM(`left`) AS totalLeft') + ->from(TABLE_TASK) + ->where('project')->eq((int)$projectID) + ->andWhere('status')->ne('cancel') + ->andWhere('deleted')->eq(0) + ->fetch(); + $project->days = $project->days ? $project->days : ''; + $project->totalHours = $this->dao->select('sum(days * hours) AS totalHours')->from(TABLE_TEAM)->where('project')->eq($project->id)->fetch('totalHours'); + $project->totalEstimate = round($total->totalEstimate, 1); + $project->totalConsumed = round($total->totalConsumed, 1); + $project->totalLeft = round($total->totalLeft, 1); + + if($setImgSize) $project->desc = $this->loadModel('file')->setImgSize($project->desc); + if($setImgSize) $project->goal = $this->loadModel('file')->setImgSize($project->goal); + + return $project; + } + + /** + * Get the default managers for a project from it's related products. + * + * @param int $projectID + * @access public + * @return object + */ + public function getDefaultManagers($projectID) + { + $managers = $this->dao->select('PO,QM,RM')->from(TABLE_PRODUCT)->alias('t1') + ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.product') + ->where('t2.project')->eq($projectID) + ->fetch(); + if($managers) return $managers; + + $managers->PO = ''; + $managers->QM = ''; + $managers->RM = ''; + return $managers; + } + + /** + * Get products of a project. + * + * @param int $projectID + * @access public + * @return array + */ + public function getProducts($projectID) + { + return $this->dao->select('t2.id, t2.name')->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t1.project')->eq((int)$projectID) + ->fetchPairs(); + } + + /** + * Update products of a project. + * + * @param int $projectID + * @access public + * @return void + */ + public function updateProducts($projectID) + { + $this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq((int)$projectID)->exec(); + if(!isset($_POST['products'])) return; + $products = array_unique($_POST['products']); + foreach($products as $productID) + { + $data->project = $projectID; + $data->product = $productID; + $this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec(); + } + } + + /** + * Get related projects + * + * @param int $projectID + * @access public + * @return array + */ + public function getRelatedProjects($projectID) + { + $products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq((int)$projectID)->fetchAll('product'); + // $products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->fetchAll('product'); + if(!$products) return array(); + $products = array_keys($products); + return $this->dao->select('t1.id, t1.name')->from(TABLE_PROJECT)->alias('t1') + ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2') + ->on('t1.id = t2.project') + ->where('t2.product')->in($products) + ->andWhere('t1.id')->ne((int)$projectID) + ->andWhere('t1.deleted')->eq(0) + ->orderBy('t1.id') + ->fetchPairs(); + } + + /** + * Get rasks can be imported. + * + * @param int $projectID + * @access public + * @return array + */ + public function getTasks2Imported($fromProject) + { + $this->loadModel('task'); + $tasks = array(); + $projectTasks = $this->task->getProjectTasks($fromProject, 'wait,doing,cancel'); + $tasks = array_merge($tasks, $projectTasks); + return $tasks; + } + + /** + * Import tasks. + * + * @param int $projectID + * @access public + * @return void + */ + public function importTask($projectID) + { + $this->loadModel('task'); + + /* Update tasks. */ + $tasks = $this->dao->select('id, project, assignedTo, story, consumed,status')->from(TABLE_TASK)->where('id')->in($this->post->tasks)->fetchAll('id'); + foreach($tasks as $task) + { + /* Save the assignedToes and stories, should linked to project. */ + $assignedToes[$task->assignedTo] = $task->project; + $stories[$task->story] = $task->story; + + $data = new stdclass(); + $data->project = $projectID; + + if($task->status == 'cancel') + { + $data->canceledBy = ''; + $data->canceledDate = NULL; + } + + $data->status = $task->consumed > 0 ? 'doing' : 'wait'; + $data->statusCustom = strpos(TASKMODEL::CUSTOM_STATUS_ORDER, $data->status) + 1; + $this->dao->update(TABLE_TASK)->data($data)->where('id')->in($this->post->tasks)->exec(); + $this->loadModel('action')->create('task', $task->id, 'moved', '', $task->project); + } + + /* Remove empty story. */ + unset($stories[0]); + + /* Add members to project team. */ + $teamMembers = $this->getTeamMemberPairs($projectID); + foreach($assignedToes as $account => $preProjectID) + { + if(!isset($teamMembers[$account])) + { + $role = $this->dao->select('*')->from(TABLE_TEAM)->where('project')->eq($preProjectID)->andWhere('account')->eq($account)->fetch(); + $role->project = $projectID; + $role->join = helper::today(); + $this->dao->insert(TABLE_TEAM)->data($role)->exec(); + } + } + + /* Link stories. */ + $projectStories = $this->loadModel('story')->getProjectStoryPairs($projectID); + foreach($stories as $storyID) + { + if(!isset($projectStories[$storyID])) + { + $story = $this->dao->findById($storyID)->fields("$projectID as project, id as story, product, version")->from(TABLE_STORY)->fetch(); + $this->dao->insert(TABLE_PROJECTSTORY)->data($story)->exec(); + } + } + } + + /** + * Import task from Bug. + * + * @param int $projectID + * @access public + * @return void + */ + public function importBug($projectID) + { + $this->loadModel('bug'); + $bugLang = $this->app->loadLang('bug'); + $this->loadModel('task'); + $this->loadModel('story'); + + $now = helper::now(); + $BugToTasks = fixer::input('post')->get(); + foreach($BugToTasks->import as $key => $value) + { + $bug = $this->bug->getById($key); + $task->project = $projectID; + $task->story = $bug->story; + $task->storyVersion = $bug->story; + $task->fromBug = $key; + $task->name = $bug->title; + $task->type = 'devel'; + $task->pri = $BugToTasks->pri[$key]; + $task->consumed = 0; + $task->status = 'wait'; + $task->statusCustom = strpos(taskModel::CUSTOM_STATUS_ORDER, 'wait') + 1; + $task->desc = $bugLang->bug->resolve . ':' . '#' . html::a(helper::createLink('bug', 'view', "bugID=$key"), sprintf('%03d', $key)); + $task->openedDate = $now; + $task->openedBy = $this->app->user->account; + if(!empty($BugToTasks->estimate[$key])) + { + $task->estimate = $BugToTasks->estimate[$key]; + $task->left = $task->estimate; + } + if(!empty($BugToTasks->assignedTo[$key])) + { + $task->assignedTo = $BugToTasks->assignedTo[$key]; + $task->assignedDate = $now; + } + $this->dao->insert(TABLE_TASK)->data($task)->checkIF($BugToTasks->estimate[$key] != '', 'estimate', 'float')->exec(); + + if(dao::isError()) + { + echo js::error(dao::getError()); + die(js::reload('parent')); + } + + $taskID = $this->dao->lastInsertID(); + if($task->story != false) $this->story->setStage($task->story); + $actionID = $this->loadModel('action')->create('task', $taskID, 'Opened', ''); + $this->action->create('bug', $key, 'Totask', '', $taskID); + $this->dao->update(TABLE_BUG)->set('toTask')->eq($taskID)->where('id')->eq($key)->exec(); + $mails[$key]->taskID = $taskID; + $mails[$key]->actionID = $actionID; + } + return $mails; + } + + /** + * Get child projects. + * + * @param int $projectID + * @access public + * @return void + */ + public function getChildProjects($projectID) + { + return $this->dao->select('id, name')->from(TABLE_PROJECT)->where('parent')->eq((int)$projectID)->fetchPairs(); + } + + /** + * Update childs. + * + * @param int $projectID + * @access public + * @return void + */ + public function updateChilds($projectID) + { + $sql = "UPDATE " . TABLE_PROJECT . " SET parent = 0 WHERE parent = '$projectID'"; + $this->dbh->exec($sql); + if(!isset($_POST['childs'])) return; + $childs = array_unique($_POST['childs']); + foreach($childs as $childProjectID) + { + $sql = "UPDATE " . TABLE_PROJECT . " SET parent = '$projectID' WHERE id = '$childProjectID'"; + $this->dbh->query($sql); + } + } + + /** + * Link story. + * + * @param int $projectID + * @access public + * @return void + */ + public function linkStory($projectID) + { + if($this->post->stories == false) return false; + $this->loadModel('action'); + $versions = $this->loadModel('story')->getVersions($this->post->stories); + foreach($this->post->stories as $key => $storyID) + { + $productID = $this->post->products[$key]; + $data->project = $projectID; + $data->product = $productID; + $data->story = $storyID; + $data->version = $versions[$storyID]; + $this->dao->insert(TABLE_PROJECTSTORY)->data($data)->exec(); + $this->story->setStage($storyID); + $this->action->create('story', $storyID, 'linked2project', '', $projectID); + } + } + + /** + * Unlink story. + * + * @param int $projectID + * @param int $storyID + * @access public + * @return void + */ + public function unlinkStory($projectID, $storyID) + { + $this->dao->delete()->from(TABLE_PROJECTSTORY)->where('project')->eq($projectID)->andWhere('story')->eq($storyID)->limit(1)->exec(); + $this->loadModel('story')->setStage($storyID); + $this->loadModel('action')->create('story', $storyID, 'unlinkedfromproject', '', $projectID); + $tasks = $this->dao->select('id')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('project')->eq($projectID)->andWhere('status')->in('wait,doing')->fetchPairs('id'); + $this->dao->update(TABLE_TASK)->set('status')->eq('cancel')->where('id')->in($tasks)->exec(); + foreach($tasks as $taskID) + { + $changes = $this->loadModel('task')->cancel($taskID); + $actionID = $this->action->create('task', $taskID, 'Canceled'); + $this->action->logHistory($actionID, $changes); + } + } + + /** + * Get team members. + * + * @param int $projectID + * @access public + * @return array + */ + public function getTeamMembers($projectID) + { + return $this->dao->select('t1.*, t1.hours * t1.days AS totalHours, t2.realname')->from(TABLE_TEAM)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account') + ->where('t1.project')->eq((int)$projectID) + ->andWHere('t2.company')->eq($this->app->company->id) + ->fetchAll('account'); + } + + /** + * Get team members in pair. + * + * @param int $projectID + * @param string $params + * @access public + * @return array + */ + public function getTeamMemberPairs($projectID, $params = '') + { + $users = $this->dao->select('t1.account, t2.realname')->from(TABLE_TEAM)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account') + ->where('t1.project')->eq((int)$projectID) + ->andWHere('t2.company')->eq($this->app->company->id) + ->beginIF($params == 'nodeleted') + ->andWhere('t2.deleted')->eq(0) + ->fi() + ->fetchPairs(); + if(!$users) return array(); + foreach($users as $account => $realName) + { + $firstLetter = ucfirst(substr($account, 0, 1)) . ':'; + $users[$account] = $firstLetter . ($realName ? $realName : $account); + } + return array('' => '') + $users; + } + + /** + * Manage team members. + * + * @param int $projectID + * @access public + * @return void + */ + public function manageMembers($projectID) + { + extract($_POST); + + $accounts = array_unique($accounts); + foreach($accounts as $key => $account) + { + if(empty($account)) continue; + + $member->role = $roles[$key]; + $member->days = $days[$key]; + $member->hours = $hours[$key]; + $mode = $modes[$key]; + + if($mode == 'update') + { + $this->dao->update(TABLE_TEAM)->data($member)->where('project')->eq((int)$projectID)->andWhere('account')->eq($account)->exec(); + } + else + { + $member->project = (int)$projectID; + $member->account = $account; + $member->join = helper::today(); + $this->dao->insert(TABLE_TEAM)->data($member)->exec(); + } + } + } + + /** + * Unlink a member. + * + * @param int $projectID + * @param string $account + * @access public + * @return void + */ + public function unlinkMember($projectID, $account) + { + $this->dao->delete()->from(TABLE_TEAM)->where('project')->eq((int)$projectID)->andWhere('account')->eq($account)->exec(); + } + + /** + * Compute burn of a project. + * + * @access public + * @return array + */ + public function computeBurn() + { + $today = helper::today(); + $burns = array(); + + $projects = $this->dao->select('id, name')->from(TABLE_PROJECT) + ->where("end >= '$today'") + ->orWhere('end')->eq('0000-00-00') + ->fetchPairs(); + if(!$projects) return $burns; + + $burns = $this->dao->select("project, '$today' AS date, sum(`left`) AS `left`, SUM(consumed) AS `consumed`") + ->from(TABLE_TASK) + ->where('project')->in(array_keys($projects)) + ->andWhere('deleted')->eq('0') + ->andWhere('status')->notin('cancel,closed') + ->groupBy('project') + ->fetchAll(); + + foreach($burns as $Key => $burn) + { + $this->dao->replace(TABLE_BURN)->data($burn)->exec(); + $burn->projectName = $projects[$burn->project]; + } + return $burns; + } + + /** + * Get data of burn down chart. + * + * @param int $projectID + * @param int $itemCounts + * @param string $mode noempty: skip the dates without burn down data. + * @access public + * @return array + */ + public function getBurnData($projectID = 0, $itemCounts = 30, $mode = 'noempty') + { + /* Get project and burn counts. */ + $project = $this->getById($projectID); + $burnCounts = $this->dao->select('count(*) AS counts')->from(TABLE_BURN)->where('project')->eq($projectID)->fetch('counts'); + + /* If the burnCounts > $itemCounts, get the latest $itemCounts records. */ + $sql = $this->dao->select('date AS name, `left` AS value')->from(TABLE_BURN)->where('project')->eq((int)$projectID); + if($burnCounts > $itemCounts) + { + $sets = $sql->orderBy('date DESC')->limit($itemCounts)->fetchAll('name'); + $sets = array_reverse($sets); + } + else + { + /* The burnCounts < itemCounts, after getting from the db, padding left dates. */ + $sets = $sql->orderBy('date ASC')->fetchAll('name'); + $this->processBurnData($sets, $itemCounts, $project->begin, $project->end, $mode); + } + + foreach($sets as $set) $set->name = substr($set->name, 5); + return $sets; + } + + /** + * Get burn data for flot + * + * @param int $projectID + * @param int $itemCounts + * @access public + * @return void + */ + public function getBurnDataFlot($projectID = 0, $itemCounts = 30) + { + /* Get project and burn counts. */ + $project = $this->getById($projectID); + $burnCounts = $this->dao->select('count(*) AS counts')->from(TABLE_BURN)->where('project')->eq($projectID)->fetch('counts'); + + /* If the burnCounts > $itemCounts, get the latest $itemCounts records. */ + $sql = $this->dao->select('date AS name, `left` AS value')->from(TABLE_BURN)->where('project')->eq((int)$projectID); + if($burnCounts > $itemCounts) + { + $sets = $sql->orderBy('date DESC')->limit($itemCounts)->fetchAll('name'); + $sets = array_reverse($sets); + } + else + { + /* The burnCounts < itemCounts, after getting from the db, padding left dates. */ + $sets = $sql->orderBy('date ASC')->fetchAll('name'); + $sets = $this->processBurnData($sets, $itemCounts, $project->begin, $project->end); + } + + $count = 0; + foreach($sets as $set) + { + $set->name = (string)strtotime("$set->name UTC") . '000'; + $count ++; + } + $sets['count'] = $count; + return $sets; + } + + /** + * Process burndown datas when the sets is smaller than the itemCounts. + * + * @param array $sets + * @param int $itemCounts + * @param date $begin + * @param date $end + * @param string $mode + * @access public + * @return array + */ + public function processBurnData($sets, $itemCounts, $begin, $end, $mode = 'noempty') + { + $burnCounts = count($sets); + $current = helper::today(); + + if($end != '0000-00-00') + { + $period = helper::diffDate($end, $begin) + 1; + $counts = $period > $itemCounts ? $itemCounts : $period; + } + else + { + $counts = $itemCounts; + } + + for($i = 0; $i < $counts - $burnCounts; $i ++) + { + if(helper::diffDate($current, $end) > 0) break; + if(!isset($sets[$current]) and $mode != 'noempty') + { + $sets[$current]->name = $current; + $sets[$current]->value = ''; + } + $nextDay = date(DT_DATE1, strtotime('next day', strtotime($current))); + $current = $nextDay; + } + return $sets; + } + + /** + * Get taskes by search. + * + * @param string $condition + * @param object $pager + * @param string $orderBy + * @access public + * @return array + */ + public function getSearchTasks($condition, $pager, $orderBy) + { + $taskIdList = $this->dao->select('id') + ->from(TABLE_TASK) + ->where($condition) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + + $tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_USER)->alias('t3')->on('t1.assignedTo = t3.account') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.id')->in(array_keys($taskIdList)) + ->orderBy($orderBy) + ->fetchAll(); + $this->loadModel('task')->processTasks($tasks); + return $tasks; + } + + /** + * Get bugs by search in project. + * + * @param int $products + * @param int $projectID + * @param int $sql + * @param int $pager + * @param int $orderBy + * @access public + * @return void + */ + public function getSearchBugs($products, $projectID, $sql, $pager, $orderBy) + { + return $this->dao->select('*')->from(TABLE_BUG) + ->where($sql) + ->andWhere('status')->eq('active') + ->andWhere('toTask')->eq(0) + ->andWhere('tostory')->eq(0) + ->beginIF(!empty($products))->andWhere('product')->in(array_keys($products))->fi() + ->beginIF(empty($products))->andWhere('project')->eq($projectID)->fi() + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + } +} diff --git a/trunk/module/project/view/browse.html.php b/trunk/module/project/view/browse.html.php new file mode 100644 index 0000000000..df0f029fbc --- /dev/null +++ b/trunk/module/project/view/browse.html.php @@ -0,0 +1,154 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + +
      +
      + + + + + + + + + + + + + + + + + + + + + + +
      + project->selectProject;?> + id, 'onchange="selectProject(this.value);" style="width:200px"');?> +
      project->name;?>name;?>
      project->code;?>code;?>
      project->begin;?>begin;?>
      project->end;?>end;?>
      + createLink('project', 'edit', "projectID=$project->id"), $lang->project->edit); + if(common::hasPriv('project', 'delete')) echo html::a($this->createLink('project', 'delete', "projectID=$project->id"), $lang->project->delete, 'hiddenwin'); + //echo html::a($this->createLink('tree', 'browse', "productID=$productID&view=product"), $lang->tree->manage); + ?> +
      + + + + + +
      + project->products;?> +
      + $productName) echo html::a($this->createLink('product', 'browse', "productID=$productID"), $productName) . '
      ';?> +
      + createLink('project', 'manageproducts', "projectID=$project->id"), $lang->project->manageProducts); + if(common::hasPriv('project', 'linkstory')) echo html::a($this->createLink('project', 'linkstory', "projectID=$project->id"), $lang->project->linkStory); + ?> +
      +
      + + + + + + + + + + $lang->actions";?> + + + + + + + + + + " . html::a($this->createLink('project', 'unlinkmember', "projectID=$project->id&account=$member->account"), $lang->project->unlinkMember, 'hiddenwin') . '';?> + + + +
      + project->team . $lang->colon . $project->team; ?> +
      team->account;?>team->role;?>team->joinDate;?>team->workingHour;?>
      + createLink('user', 'view', "account=$member->account"), $member->realname); + else echo $member->realname; + ?> + role;?>joinDate, 2);?>workingHour;?>
      +
      + createLink('project', 'managemembers', "projectID=$project->id"), $lang->project->manageMembers) . '
      ';?> +
      + +
      +
      +
      +
      +
        + $project->name"; + echo "
      • " . html::a($this->createLink('project', 'browse', "projectID=$project->id&tabID=task"), $lang->project->tasks) . "
      • "; + echo "
      • " . html::a($this->createLink('project', 'browse', "projectID=$project->id&tabID=story"), $lang->project->stories) . "
      • "; + //echo "
      • " . html::a($this->createLink('project', 'browse', "projectID=$project->id&tabID=bug"), $lang->project->bugs) . "
      • "; + //echo "
      • " . html::a($this->createLink('project', 'browse', "projectID=$project->id&tabID=burn"), $lang->project->burndown) . "
      • "; + echo << +$("#{$tabID}tab").addClass('active'); + +EOT; + ?> +
      + +
      createLink('task', 'create', "project=$project->id"), $lang->task->create);?>
      + +
      createLink('project', 'linkstory', "project=$project->id"), $lang->project->linkStory);?>
      + +
      + +
      +
      +
      + diff --git a/trunk/module/project/view/bug.html.php b/trunk/module/project/view/bug.html.php new file mode 100644 index 0000000000..a4cf6c33bc --- /dev/null +++ b/trunk/module/project/view/bug.html.php @@ -0,0 +1,63 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + id}&orderBy=%s&build=$buildID&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}"; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + project->bug; + if($build) echo '(Build:' . $build->name . ')'; + ?> +
      +
      id");?>
      +
      idAB);?>bug->severityAB);?> priAB);?> bug->title);?> openedByAB);?> assignedToAB);?> bug->resolvedBy);?>bug->resolutionAB);?>actions;?>
      createLink('bug', 'view', "bugID=$bug->id"), $bug->id, '_blank');?>bug->severityList[$bug->severity]?>bug->priList[$bug->pri]?>createLink('bug', 'view', "bugID=$bug->id"), $bug->title);?>openedBy];?>assignedTo];?>resolvedBy];?>bug->resolutionList[$bug->resolution];?> + id"; + common::printIcon('bug', 'resolve', $params, $bug, 'list'); + common::printIcon('bug', 'close', $params, $bug, 'list'); + common::printIcon('bug', 'edit', $params, $bug, 'list'); + ?> +
      show();?>
      + + diff --git a/trunk/module/project/view/build.html.php b/trunk/module/project/view/build.html.php new file mode 100644 index 0000000000..65dbce5726 --- /dev/null +++ b/trunk/module/project/view/build.html.php @@ -0,0 +1,54 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      project->build;?>
      +
      id", $lang->build->create);?>
      +
      build->id;?>build->product;?>build->name;?>build->scmPath;?>build->filePath;?>build->date;?>build->builder;?>actions;?>
      id;?>productName;?>createLink('build', 'view', "build=$build->id"), $build->name);?>scmPath, 'http') === 0 ? printf(html::a($build->scmPath)) : printf($build->scmPath);?>filePath, 'http') === 0 ? printf(html::a($build->filePath)) : printf($build->filePath);?>date?>builder]?> + id&build=$build->id", $lang->testtask->create); + common::printLink('project', 'bug', "project=$project->id&orderBy=status&build=$build->id", $lang->project->viewBug); + common::printLink('build', 'edit', "buildID=$build->id", ' ', '', "class='icon-green-small-edit' title='{$lang->edit}'", false); + common::printLink('build', 'delete', "buildID=$build->id", ' ', 'hiddenwin', "class='icon-green-small-delete' title='{$lang->delete}'", false); + ?> +
      + diff --git a/trunk/module/project/view/burn.html.php b/trunk/module/project/view/burn.html.php new file mode 100644 index 0000000000..5fc3d8d512 --- /dev/null +++ b/trunk/module/project/view/burn.html.php @@ -0,0 +1,23 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + project->computeBurn, 'hiddenwin'); + printf($lang->project->howToUpdateBurn, $this->createLink('help', 'field', 'module=project&method-burn&field=updateburn')); + ?> +
      + diff --git a/trunk/module/project/view/computeburn.html.php b/trunk/module/project/view/computeburn.html.php new file mode 100644 index 0000000000..10b5c1811a --- /dev/null +++ b/trunk/module/project/view/computeburn.html.php @@ -0,0 +1,18 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> +project . "\t" . $burn->projectName . "\t" . $burn->date . "\t" . $burn->left . "\n"; +} +?> diff --git a/trunk/module/project/view/create.html.php b/trunk/module/project/view/create.html.php new file mode 100644 index 0000000000..c8504b3141 --- /dev/null +++ b/trunk/module/project/view/create.html.php @@ -0,0 +1,100 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + +
      + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + +
      +
      project->create;?>
      +
      project->copy . '?', '', 'onclick=switchCopyProject(this);');?>
      +
      project->copy;?>
      project->name;?>
      project->code;?>
      project->begin;?>
      project->end;?> + + project->endList , '', "onclick='computeEndDate(this.value)'");?> +
      project->days;?>project->day;?>
      project->teamname;?>
      project->manageProducts;?>
      project->goal;?>
      project->desc;?>
      project->acl;?>project->aclList, $acl, "onclick='setWhite(this.value);'"));?>
      project->whitelist;?>
      +
      + diff --git a/trunk/module/project/view/doc.html.php b/trunk/module/project/view/doc.html.php new file mode 100644 index 0000000000..b753eb56a0 --- /dev/null +++ b/trunk/module/project/view/doc.html.php @@ -0,0 +1,51 @@ + + * @package product + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + $doc):?> + createLink('doc', 'view', "docID=$doc->id"); + $canView = common::hasPriv('doc', 'view'); + ?> + + + + + + + + + + +
      id&from=project", $lang->doc->create);?>
      idAB;?>doc->module;?>doc->title;?>doc->addedBy;?>doc->addedDate;?>actions;?>
      id)); else printf('%03d', $doc->id);?>module]);?>title);?>addedBy];?>addedDate;?> + id}"; + if(!common::printLink('doc', 'edit', $vars, $lang->edit)) echo $lang->edit; + if(!common::printLink('doc', 'delete', $vars, $lang->delete, 'hiddenwin')) echo $lang->delete; + ?> +
      + diff --git a/trunk/module/project/view/dynamic.html.php b/trunk/module/project/view/dynamic.html.php new file mode 100755 index 0000000000..7be0fc5b92 --- /dev/null +++ b/trunk/module/project/view/dynamic.html.php @@ -0,0 +1,56 @@ +dynamic view file of dashboard module of ZenTaoPMS. + * + * @copyright Copyright 2009-2012 青岛易软天创网络科技有限公司 (QingDao Nature Easy Soft Network Technology Co,LTD www.cnezsoft.com) + * @license LGPL (http://www.gnu.org/licenses/lgpl.html) + * @author Chunsheng Wang + * @package dashboard + * @version $Id: action->dynamic.html.php 1477 2011-03-01 15:25:50Z wwccss $ + * @link http://www.zentao.net + */ +?> + + +
      + ' . html::a(inlink('dynamic', "projectID=$projectID&type=today"), $lang->action->dynamic->today) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=yesterday"), $lang->action->dynamic->yesterday) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=twodaysago"), $lang->action->dynamic->twoDaysAgo) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=thisweek"), $lang->action->dynamic->thisWeek) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=lastweek"), $lang->action->dynamic->lastWeek) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=thismonth"), $lang->action->dynamic->thisMonth) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=lastmonth"), $lang->action->dynamic->lastMonth) . ''; + echo '' . html::a(inlink('dynamic', "projectID=$projectID&type=all"), $lang->action->dynamic->all) . ''; + echo "" . html::select('account', $users, $account, "onchange=changeUser(this.value,$projectID)") . ''; + ?> +
      + + + + + + + + + + + + + + + objectType == 'case' ? 'testcase' : $action->objectType;?> + + + + + + + + + + + +
      action->date;?> action->actor;?>action->action;?> action->objectType;?> idAB;?>action->objectName;?>
      date;?>actor]) ? print($users[$action->actor]) : print($action->actor);?>actionLabel;?>action->objectTypes[$action->objectType];?>objectID;?>objectLink, $action->objectName);?>
      show();?>
      + + diff --git a/trunk/module/project/view/edit.html.php b/trunk/module/project/view/edit.html.php new file mode 100644 index 0000000000..3104e4cc3a --- /dev/null +++ b/trunk/module/project/view/edit.html.php @@ -0,0 +1,86 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + acl != 'custom') echo "class='hidden'";?>> + + + + +
      project->edit;?>
      project->name;?>name, "class='text-3'");?>
      project->code;?>code, "class='text-3'");?>
      project->begin;?>begin, "class='text-3 date' onchange='computeWorkDays()'");?>
      project->end;?>end, "class='text-3 date' onchange='computeWorkDays()'");?>
      project->days;?>days, "class='text-3'") . $lang->project->day;?>
      project->teamname;?>team, "class='text-3'");?>
      project->status;?>project->statusList, $project->status, 'class=text-3');?>
      project->PO;?>PO, 'class=text-3');?>
      project->PM;?>PM, 'class=text-3');?>
      project->QM;?>QM, 'class=text-3');?>
      project->RM;?>RM, 'class=text-3');?>
      project->manageProducts;?>
      project->goal;?>goal), "rows='6' class='area-1'");?>
      project->desc;?>desc), "rows='6' class='area-1'");?>
      project->acl;?>project->aclList, $project->acl, "onclick='setWhite(this.value);'"));?>
      project->whitelist;?>whitelist);?>
      +
      + diff --git a/trunk/module/project/view/grouptask.html.php b/trunk/module/project/view/grouptask.html.php new file mode 100644 index 0000000000..de40e72529 --- /dev/null +++ b/trunk/module/project/view/grouptask.html.php @@ -0,0 +1,118 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + $groupTasks):?> + + + + + + + + assignedTo == $app->user->account ? 'style=color:red' : '';?> + createLink('task','view',"taskID=$task->id"); ?> + estimate; + $totalConsumed += $task->consumed; + $totalLeft += ($task->status == 'cancel' ? 0 : $task->left); + + $groupEstimate += $task->estimate; + $groupConsumed += $task->consumed; + $groupLeft += ($task->status == 'cancel' ? 0 : $task->left); + + if($task->status == 'wait') + { + $statusWait++; + $groupWait++; + } + elseif($task->status == 'doing') + { + $statusDoing++; + $groupDoing++; + } + elseif($task->status == 'done') + { + $statusDone++; + $groupDone++; + } + elseif($task->status == 'closed') + { + $statusClosed++; + $groupClosed++; + } + $groupSum = count($groupTasks); + $taskSum += count($tasks); + ?> + + + + + + + + + + + + + + + + + + + +
      task->name;?> priAB;?>task->assignedTo;?>task->finishedBy;?>task->estimateAB;?>task->consumedAB;?>task->leftAB;?>typeAB;?>task->deadlineAB;?>task->status;?>
       id . $lang->colon; if(common::hasPriv('task', 'view')) echo html::a($this->createLink('task', 'view', "task=$task->id"), $task->name); else echo $task->name;?>pri;?>>assignedToRealName;?>finishedBy];?>estimate;?>consumed;?>left;?>task->typeList[$task->type];?>delay)) echo 'delayed';?>>deadline, 0, 4) > 0) echo $task->deadline;?>status;?> >task->statusList[$task->status];?> + createLink('task', 'edit', "taskid=$task->id"), $lang->edit);?> + createLink('task', 'delete', "projectID=$task->project&taskid=$task->id"), $lang->delete, 'hiddenwin');?> +
      + assignedTo])) printf($lang->project->memberHours, $users[$task->assignedTo], $members[$task->assignedTo]->totalHours);?> + project->groupSummary, $groupSum, $groupWait, $groupDoing, $groupEstimate, $groupConsumed, $groupLeft);?> +
      + + diff --git a/trunk/module/project/view/importbug.html.php b/trunk/module/project/view/importbug.html.php new file mode 100755 index 0000000000..a5cd9a0eb1 --- /dev/null +++ b/trunk/module/project/view/importbug.html.php @@ -0,0 +1,62 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      import;?> idAB;?> bug->severityAB;?> priAB;?>bug->title;?>bug->statusAB;?>task->pri;?>task->assignedTo;?>task->estimate;?>
      id]", '');?> id) . html::hidden("id[$bug->id]", $bug->id);?>bug->severityList[$bug->severity]?>bug->priList[$bug->pri]?>id", $bug->title, '', "class='preview'");?>bug->statusList[$bug->status];?>id]", $lang->task->priList, 3);?>id]", $users, '');?>id]", '', 'size=4');?>
      show();?>
      +
      import) . html::resetButton();?>
      +
      +
      + diff --git a/trunk/module/project/view/importtask.html.php b/trunk/module/project/view/importtask.html.php new file mode 100644 index 0000000000..ec86cf6891 --- /dev/null +++ b/trunk/module/project/view/importtask.html.php @@ -0,0 +1,59 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + + + + + + assignedTo == $app->user->account ? 'style=color:red' : '';?> + + + + + + + + + + + + + + +
      $lang->project->fromproject)+$projects;?>idAB;?>priAB;?>task->name;?>task->assignedTo;?>task->leftAB;?>task->deadlineAB;?>statusAB;?>task->story;?>import;?>
      project];?>id", sprintf('%03d', $task->id))) printf('%03d', $task->id);?>pri;?>id", $task->name)) echo $task->name;?>>assignedToRealName;?>left;?>delay)) echo 'delayed';?>>deadline, 0, 4) > 0) echo $task->deadline;?>status;?> >task->statusList[$task->status];?> + storyID) + { + if(common::hasPriv('story', 'view')) echo html::a($this->createLink('story', 'view', "storyid=$task->storyID"), $task->storyTitle); + else echo $task->storyTitle; + } + ?> +
      +
      project->importTask);?>
      +
      + diff --git a/trunk/module/project/view/index.html.php b/trunk/module/project/view/index.html.php new file mode 100644 index 0000000000..4df7cf4d01 --- /dev/null +++ b/trunk/module/project/view/index.html.php @@ -0,0 +1,48 @@ + + * @package ZenTaoPMS + * @version $Id$ + */ +?> + + + +

      + project->unDoneProjects);?> + project->doneProjects);?> +

      + + + + + + + + + + + + + + + + + + + + + + + + + +
      project->name;?>project->code;?>project->end;?>project->status;?>project->totalEstimate;?>project->totalConsumed;?>project->totalLeft;?>project->progess;?>project->burn;?>
      createLink('project', 'task', 'project=' . $project->id), $project->name, '_parent');?>code;?>end;?>project->statusList[$project->status];?>hours->totalEstimate;?>hours->totalConsumed;?>hours->totalLeft;?> + hours->progress;?> height='13' text-align: /> + hours->progress;?>% + burns);?>'>
      + diff --git a/trunk/module/project/view/linkstory.html.php b/trunk/module/project/view/linkstory.html.php new file mode 100644 index 0000000000..5bf553198e --- /dev/null +++ b/trunk/module/project/view/linkstory.html.php @@ -0,0 +1,62 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      +
      + + + + + + + + + + + + + + + + id])) continue;?> + createLink('story', 'view', "storyID=$story->id");?> + + + + + + + + + + + + + + + + + +
      idAB;?>priAB;?>story->product;?>story->title;?>story->plan;?>openedByAB;?>story->estimateAB;?>
      + + + id);?> + story->priList[$story->pri];?>createLink('product', 'browse', "productID=$story->product"), $products[$story->product], '_blank');?>title);?>planTitle;?>openedBy];?>estimate;?>
      + project->whyNoStories; + ?> +
      +
      + diff --git a/trunk/module/project/view/managechilds.html.php b/trunk/module/project/view/managechilds.html.php new file mode 100644 index 0000000000..7b7c4c0e14 --- /dev/null +++ b/trunk/module/project/view/managechilds.html.php @@ -0,0 +1,29 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +
      + + + + + + +
      project->manageChilds;?>
      + +
      +
      +
      + diff --git a/trunk/module/project/view/managemembers.html.php b/trunk/module/project/view/managemembers.html.php new file mode 100644 index 0000000000..032d281df4 --- /dev/null +++ b/trunk/module/project/view/managemembers.html.php @@ -0,0 +1,60 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + $member):?> + account], 2);?> + account]);?> + + + + + + + + + PROJECTMODEL::LINK_MEMBERS_ONE_TIME) $count = PROJECTMODEL::LINK_MEMBERS_ONE_TIME; + ?> + + + + + + + + + + + + +
      project->manageMembers;?>
      team->account;?>team->role;?>team->days;?>team->hours;?>
      + + +
      + + +
      + +
      +
      + diff --git a/trunk/module/project/view/manageproducts.html.php b/trunk/module/project/view/manageproducts.html.php new file mode 100644 index 0000000000..c0da830d90 --- /dev/null +++ b/trunk/module/project/view/manageproducts.html.php @@ -0,0 +1,23 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + +
      project->manageProducts;?>
      +
      + diff --git a/trunk/module/project/view/order.html.php b/trunk/module/project/view/order.html.php new file mode 100644 index 0000000000..0ee09e24ea --- /dev/null +++ b/trunk/module/project/view/order.html.php @@ -0,0 +1,55 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + status == 'done') continue;?> + + + + + + + + +
      project->statusUndone?>
      project->id?>project->name?>project->status?>project->order?>
      id?>name?>project->statusList[$project->status]?>id, $project->order, "size='5'")?>
      +
      +
      + + + + + + + + + status != 'done') continue;?> + + + + + + + +
      project->statusDone?>
      project->id?>project->name?>project->order?>
      id?>name?>id, $project->order, "size='5'")?>
      +
      + + diff --git a/trunk/module/project/view/project.html.php b/trunk/module/project/view/project.html.php new file mode 100644 index 0000000000..0cdf82a8fc --- /dev/null +++ b/trunk/module/project/view/project.html.php @@ -0,0 +1,12 @@ +project->oneLineStats, + $project->name, + $project->code, + $products, + $project->begin, + $project->end, + $project->totalEstimate, + $project->totalConsumed, + $project->totalLeft); +?> diff --git a/trunk/module/project/view/sendmail.html.php b/trunk/module/project/view/sendmail.html.php new file mode 100644 index 0000000000..50f303213f --- /dev/null +++ b/trunk/module/project/view/sendmail.html.php @@ -0,0 +1,38 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + id}&orderBy=%s"; ?> + + + + + + + + + + + + + + + $story):?> + createLink('story', 'view', "storyID=$story->id"); + $totalEstimate += $story->estimate; + ?> + + + + + + + + + + + + + + + + + + + +
      + TASK #id . "=>$task->assignedTo " . html::a(common::getSysURL() . $this->createLink('task', 'view', "taskID=$task->id"), $task->name);?> +
      +
      + task->legendDesc;?> +
      + desc, 'id", $lang->story->create); + if(common::hasPriv('project', 'linkstory')) echo html::a($this->createLink('project', 'linkstory', "project=$project->id"), $lang->project->linkStory); + ?> +
      + +
      idAB);?> priAB);?> story->title);?> openedByAB);?> assignedToAB);?> story->estimateAB);?> statusAB);?> story->stageAB);?> story->taskCount;?> actions;?>
      + + id));?> + story->priList[$story->pri];?>title);?>openedBy];?>assignedTo];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?> + id}&story={$story->id}"; + common::printLink('task', 'create', $param, $lang->project->wbs); + common::printLink('project', 'unlinkStory', $param, $lang->unlink, 'hiddenwin'); + ?> +
      +
      + createLink('story', 'batchClose', "from=projectStory&productID=0&projectID=$project->id&orderBy=$orderBy"); + echo html::commonButton($lang->story->batchClose, "onclick=\"changeAction('projectStoryForm', 'batchClose', '$actionLink')\""); + } + } + printf($lang->product->storySummary, count($stories), $totalEstimate); + ?> +
      +
      + + diff --git a/trunk/module/project/view/task.html.php b/trunk/module/project/view/task.html.php new file mode 100644 index 0000000000..26a3bf987c --- /dev/null +++ b/trunk/module/project/view/task.html.php @@ -0,0 +1,169 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
      '>
      + + + + + + + + + + + + +
      +
      project->projectTasks;?>
      +
      +
      +
      name;?>
      +
      + +
      + edit);?> + delete, 'hiddenwin');?> + tree->manage);?> + tree->fix, 'hiddenwin');?> +
      +
      +
      +
      id&from=projectTask&orderBy=$orderBy");?>'> + + id&status=$status&parma=$param&orderBy=%s&recTotal=$recTotal&recPerPage=$recPerPage"; ?> + + + + + + + + + cookie->windowWidth > $this->config->wideSize):?> + + + + + + + cookie->windowWidth > $this->config->wideSize):?> + + + + + + + + + + + + + + assignedTo == $app->user->account ? 'style=color:red' : '';?> + estimate; + $totalConsumed += $task->consumed; + $totalLeft += (($task->status == 'cancel' or $task->closedReason == 'cancel') ? 0 : $task->left); + $statusVar = 'status' . ucfirst($task->status); + $$statusVar ++; + ?> + + + + + + + + cookie->windowWidth > $this->config->wideSize):?> + + + + cookie->windowWidth > $this->config->wideSize):?> + + + + + + + + + + + cookie->windowWidth > $this->config->wideSize ? 14 : 12;?> + + + +
      idAB);?> priAB);?> task->name);?>statusAB);?> task->deadlineAB);?> task->openedDateAB);?> task->assignedToAB);?> task->finishedByAB);?> task->finishedDateAB);?> task->estimateAB);?> task->consumedAB);?> task->leftAB);?>task->story);?>actions;?>
      + + id", sprintf('%03d', $task->id))) printf('%03d', $task->id);?> + task->priList[$task->pri];?> + id", $task->name)) echo $task->name; + if($task->fromBug) echo html::a($this->createLink('bug', 'view', "id=$task->fromBug"), "[BUG#$task->fromBug]", '_blank', "class='bug'"); + ?> + status;?> > + storyStatus == 'active' and $task->latestStoryVersion > $task->storyVersion); + $storyChanged ? print("{$lang->story->changed} ") : print($lang->task->statusList[$task->status]); + ?> + delay)) echo 'delayed';?>>deadline, 0, 4) > 0) echo substr($task->deadline, 5, 6);?>openedDate, 5, 6);?> + + + >assignedToRealName;?>finishedBy];?>finishedDate, 5, 6);?> + + + estimate;?>consumed;?>left;?> + storyID and common::hasPriv('story', 'view')) $story = html::a($this->createLink('story', 'view', "storyid=$task->storyID"), $task->storyTitle); + if($task->storyID and !common::hasPriv('story', 'view')) $story = $task->storyTitle; + echo $story; + ?> + + id", $lang->confirm, 'hiddenwin'); + common::printIcon('task', 'assignTo', "projectID=$task->project&taskID=$task->id", $task, 'list'); + common::printIcon('task', 'finish', "taskID=$task->id", $task, 'list'); + common::printIcon('task', 'close', "taskID=$task->id", $task, 'list'); + if($this->task->isClickable($task, 'edit')) common::printIcon('task', 'edit',"taskID=$task->id", '', 'list'); + ?> +
      +
      + task->batchEdit); + printf($lang->project->taskSummary, count($tasks), $statusWait, $statusDoing, $totalEstimate, $totalConsumed, $totalLeft); + ?> +
      + show();?> +
      +
      +
      + + diff --git a/trunk/module/project/view/taskheader.html.php b/trunk/module/project/view/taskheader.html.php new file mode 100644 index 0000000000..75cd45e9a6 --- /dev/null +++ b/trunk/module/project/view/taskheader.html.php @@ -0,0 +1,32 @@ +
      +
      + " ; common::printLink('project', 'task', "project=$projectID&type=all", $lang->project->allTasks); echo '' ; + echo "" ; common::printLink('project', 'burn', "project=$projectID", $lang->project->burn); echo '' ; + echo ""; common::printLink('project', 'task', "project=$projectID&type=assignedtome", $lang->project->assignedToMe); echo '' ; + + echo ""; + echo html::select('status', $lang->project->statusSelects, isset($status) ? $status : '', "onchange='switchStatus({$projectID}, this.value)'"); + echo ""; + + echo ""; + echo html::select('groupBy', $lang->project->groups, isset($groupBy) ? $groupBy : '', "onchange='switchGroup($projectID, this.value)'"); + echo ""; + + echo ""; common::printLink('project', 'task',"project=$projectID&type=byProject", $lang->project->projectTasks); echo ''; + echo ""; common::printLink('project', 'task',"project=$projectID&type=byModule", $lang->project->moduleTask); echo ''; + echo "{$lang->project->byQuery} "; + ?> +
      +
      + +
      +
      + diff --git a/trunk/module/project/view/team.html.php b/trunk/module/project/view/team.html.php new file mode 100644 index 0000000000..06d8d95b8d --- /dev/null +++ b/trunk/module/project/view/team.html.php @@ -0,0 +1,56 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + $lang->actions";?> + + + + + + + + + + + + + " . html::a($this->createLink('project', 'unlinkmember', "projectID=$project->id&account=$member->account"), $lang->project->unlinkMember, 'hiddenwin') . '';?> + + + + + + + + +
      team->account;?>team->role;?>team->join;?>team->days;?>team->hours;?>team->totalHours;?>
      + createLink('user', 'view', "account=$member->account"), $member->realname)) : print($member->realname); + $memberHours = $member->days * $member->hours; + $totalHours += $memberHours; + ?> + role;?>join, 2);?>days;?>hours;?>
      +
      team->totalHours . ':' . "$totalHours";?>
      +
      id", $lang->project->manageMembers);?>
      +
      + diff --git a/trunk/module/project/view/testtask.html.php b/trunk/module/project/view/testtask.html.php new file mode 100644 index 0000000000..6192d1450b --- /dev/null +++ b/trunk/module/project/view/testtask.html.php @@ -0,0 +1,54 @@ + + * @package testtask + * @version $Id: browse.html.php 1914 2011-06-24 10:11:25Z yidong@cnezsoft.com $ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      testtask->browse;?>
      +
      project->createTesttask);?>
      +
      idAB;?>testtask->name;?>testtask->build;?>testtask->owner;?>testtask->begin;?>testtask->end;?>statusAB;?>actions;?>
      createLink('testtask', 'view', "taskID=$task->id"), sprintf('%03d', $task->id));?>createLink('testtask', 'view', "taskID=$task->id"), $task->name);?>build == 'trunk' ? print('Trunk') : print(html::a($this->createLink('build', 'view', "buildID=$task->build"), $task->buildName));?>owner];?>begin?>end?>testtask->statusList[$task->status];?> + id", $lang->testtask->cases); + common::printLink('testtask', 'linkcase', "taskID=$task->id", $lang->testtask->linkCaseAB); + common::printLink('testtask', 'edit', "taskID=$task->id", $lang->edit); + common::printLink('testtask', 'delete', "taskID=$task->id", $lang->delete, 'hiddenwin'); + ?> +
      + diff --git a/trunk/module/project/view/tips.html.php b/trunk/module/project/view/tips.html.php new file mode 100644 index 0000000000..32889ce183 --- /dev/null +++ b/trunk/module/project/view/tips.html.php @@ -0,0 +1,11 @@ + + + + + +
      project->tips;?>
      project->afterInfo;?> +
      arrow. html::a($this->createLink('project', 'team', "projectID=$projectID"), $lang->project->setTeam);?>
      +
      arrow. html::a($this->createLink('project', 'linkstory', "projectID=$projectID"), $lang->project->linkStory);?>
      +
      arrow. html::a($this->createLink('task', 'create', "project=$projectID"), $lang->project->createTask);?>
      +
      arrow. html::a($this->createLink('project', 'task', "projectID=$projectID"), $lang->project->goback);?>
      +
      diff --git a/trunk/module/project/view/view.html.php b/trunk/module/project/view/view.html.php new file mode 100644 index 0000000000..00f6f5567d --- /dev/null +++ b/trunk/module/project/view/view.html.php @@ -0,0 +1,126 @@ + + * @package project + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
      + name;?> +
      + session->projectList ? $this->session->projectList : inlink('task', "projectID=$project->id"); + if(!$project->deleted) + { + common::printLink('project', 'edit', "projectID=$project->id", $lang->project->edit); + common::printLink('project', 'delete', "projectID=$project->id", $lang->project->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      +
      +
      + project->desc;?> +
      desc;?>
      +
      + +
      + session->projectList ? $this->session->projectList : inlink('task', "projectID=$project->id"); + if(!$project->deleted) + { + common::printLink('project', 'edit', "projectID=$project->id", $lang->project->edit); + common::printLink('project', 'delete', "projectID=$project->id", $lang->project->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      +
      +
      + project->basicInfo?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      project->name;?>deleted) echo "class='deleted'";?>>name;?>
      project->code;?>code;?>
      project->beginAndEnd;?>begin . ' ~ ' . $project->end;?>
      project->days;?>days;?>
      project->goal;?>goal;?>
      project->status;?>show($lang->project->statusList, $project->status);?>
      project->PM;?>PM];?>
      project->PO;?>PO];?>
      project->QM;?>QM];?>
      project->RM;?>RM];?>
      project->products;?> + $productName) echo html::a($this->createLink('product', 'browse', "productID=$productID"), $productName) . '
      ';?> +
      project->acl;?>project->aclList[$project->acl];?>
      project->whitelist;?> + whitelist); + foreach($whitelist as $groupID) if(isset($groups[$groupID])) echo $groups[$groupID] . ' '; + ?> +
      +
      +
      + project->otherInfo?> + + + + + +
      project->lblStats;?>project->stats, $project->totalHours, $project->totalEstimate, $project->totalConsumed, $project->totalLeft, 10)?>
      +
      +
      + diff --git a/trunk/module/qa/control.php b/trunk/module/qa/control.php new file mode 100644 index 0000000000..2ddac19bb0 --- /dev/null +++ b/trunk/module/qa/control.php @@ -0,0 +1,24 @@ + + * @package qa + * @version $Id$ + * @link http://www.zentao.net + */ +class qa extends control +{ + /** + * The index of qa, go to bug's browse page. + * + * @access public + * @return void + */ + public function index() + { + $this->locate($this->createLink('bug', 'browse')); + } +} diff --git a/trunk/module/qa/lang/en.php b/trunk/module/qa/lang/en.php new file mode 100644 index 0000000000..60f80c5baf --- /dev/null +++ b/trunk/module/qa/lang/en.php @@ -0,0 +1,13 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->qa->common = 'Test'; +$lang->qa->index = "Index"; diff --git a/trunk/module/qa/lang/zh-cn.php b/trunk/module/qa/lang/zh-cn.php new file mode 100644 index 0000000000..815a69ed63 --- /dev/null +++ b/trunk/module/qa/lang/zh-cn.php @@ -0,0 +1,13 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->qa->common = '测试视图'; +$lang->qa->index = "测试首页"; diff --git a/trunk/module/qa/lang/zh-tw.php b/trunk/module/qa/lang/zh-tw.php new file mode 100644 index 0000000000..3f02d52da1 --- /dev/null +++ b/trunk/module/qa/lang/zh-tw.php @@ -0,0 +1,13 @@ + + * @package company + * @version $Id: zh-tw.php 2605 2012-02-21 07:22:58Z wwccss $ + * @link http://www.zentao.net + */ +$lang->qa->common = '測試視圖'; +$lang->qa->index = "測試首頁"; diff --git a/trunk/module/qa/model.php b/trunk/module/qa/model.php new file mode 100644 index 0000000000..bdbbca25a5 --- /dev/null +++ b/trunk/module/qa/model.php @@ -0,0 +1,15 @@ + + * @package qa + * @version $Id$ + * @link http://www.zentao.net + */ +class qaModel extends model +{ +} + diff --git a/trunk/module/qa/view/index.html.php b/trunk/module/qa/view/index.html.php new file mode 100644 index 0000000000..5040042bbc --- /dev/null +++ b/trunk/module/qa/view/index.html.php @@ -0,0 +1,16 @@ + + * @package company + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +
      + diff --git a/trunk/module/release/config.php b/trunk/module/release/config.php new file mode 100644 index 0000000000..d9aa663065 --- /dev/null +++ b/trunk/module/release/config.php @@ -0,0 +1,6 @@ +release->create->requiredFields = 'name,date'; +$config->release->edit->requiredFields = 'name,date'; + +$config->release->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->release->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/trunk/module/release/control.php b/trunk/module/release/control.php new file mode 100644 index 0000000000..e4164fb5f6 --- /dev/null +++ b/trunk/module/release/control.php @@ -0,0 +1,260 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +class release extends control +{ + /** + * Common actions. + * + * @param int $productID + * @access public + * @return void + */ + public function commonAction($productID) + { + $this->loadModel('product'); + $this->view->product = $this->product->getById($productID); + $this->view->position[] = html::a($this->createLink('product', 'browse', "productID={$this->view->product->id}"), $this->view->product->name); + $this->product->setMenu($this->product->getPairs(), $productID); + } + + /** + * Browse releases. + * + * @param int $productID + * @access public + * @return void + */ + public function browse($productID) + { + $this->commonAction($productID); + $products = $this->product->getPairs(); + $this->session->set('releaseList', $this->app->getURI(true)); + $this->view->header->title = $products[$productID] . $this->lang->colon . $this->lang->release->browse; + $this->view->position[] = $this->lang->release->browse; + $this->view->releases = $this->release->getList($productID); + $this->display(); + } + + /** + * Create a release. + * + * @param int $productID + * @access public + * @return void + */ + public function create($productID) + { + if(!empty($_POST)) + { + $releaseID = $this->release->create($productID); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action')->create('release', $releaseID, 'opened'); + die(js::locate(inlink('view', "releaseID=$releaseID"), 'parent')); + } + + $builds = $this->loadModel('build')->getProductBuildPairs($productID); + $releaseBuilds = $this->release->getReleaseBuilds($productID); + foreach($releaseBuilds as $build) unset($builds[$build]); + unset($builds['trunk']); + + $this->commonAction($productID); + $this->view->header->title = $this->lang->release->create; + $this->view->position[] = $this->lang->release->create; + $this->view->builds = $builds; + $this->view->productID = $productID; + $this->display(); + } + + /** + * Edit a release. + * + * @param int $releaseID + * @access public + * @return void + */ + public function edit($releaseID) + { + if(!empty($_POST)) + { + $changes = $this->release->update($releaseID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('release', $releaseID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate(inlink('view', "releaseID=$releaseID"), 'parent')); + } + $this->loadModel('story'); + $this->loadModel('bug'); + $this->loadModel('build'); + + /* Get release and build. */ + $release = $this->release->getById((int)$releaseID); + $this->commonAction($release->product); + $build = $this->build->getById($release->build); + if($release->build !=0) + { + /* Get stories and bugs. */ + $orderBy = 'status_asc, stage_asc, id_desc'; + $stories = $this->story->getProjectStories($build->project, $orderBy); + $bugs = $this->bug->getProjectBugs($build->project); + } + else + { + $stories = array(); + $bugs = array(); + } + + $this->view->header->title = $this->lang->release->edit; + $this->view->position[] = $this->lang->release->edit; + $this->view->release = $release; + $this->view->build = $build; + $this->view->stories = $stories; + $this->view->bugs = $bugs; + $this->view->builds = $this->loadModel('build')->getProductBuildPairs($release->product); + unset($this->view->builds['trunk']); + $this->display(); + } + + /** + * View a release. + * + * @param int $releaseID + * @access public + * @return void + */ + public function view($releaseID) + { + $this->loadModel('story'); + $this->loadModel('bug'); + + $release = $this->release->getById((int)$releaseID, true); + if(!$release) die(js::error($this->lang->notFound) . js::locate('back')); + + $stories = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($release->stories)->fetchAll(); + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story'); + + $bugs = $this->dao->select('*')->from(TABLE_BUG)->where('id')->in($release->bugs)->fetchAll(); + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug'); + + + $this->commonAction($release->product); + $products = $this->product->getPairs(); + $this->view->header->title = "RELEASE #$release->id $release->name/" . $products[$release->product]; + $this->view->position[] = $this->lang->release->view; + $this->view->release = $release; + $this->view->stories = $stories; + $this->view->bugs = $bugs; + $this->view->actions = $this->loadModel('action')->getList('release', $releaseID); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->display(); + } + + /** + * Delete a release. + * + * @param int $releaseID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($releaseID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->release->confirmDelete, $this->createLink('release', 'delete', "releaseID=$releaseID&confirm=yes"))); + } + else + { + $this->release->delete(TABLE_RELEASE, $releaseID); + die(js::locate($this->session->releaseList, 'parent')); + } + } + + /** + * Ajax get stories and bugs + * + * @param int $buildID + * @access public + * @return void + */ + public function ajaxGetStoriesAndBugs($buildID, $productID) + { + $orderBy = 'status_asc,stage_asc,id_desc'; + $stories = array(); + $bugs = array(); + $this->loadModel('bug'); + $this->loadModel('story'); + $build = $this->dao->select('project')->from(TABLE_BUILD)->where('id')->eq($buildID)->fetch(); + if(!empty($build)) + { + $stories = $this->story->getProjectStories($build->project, $orderBy); + $bugs = $this->bug->getProjectBugs($build->project); + } + $this->view->productID = $productID; + $this->view->stories = $stories; + $this->view->bugs = $bugs; + $this->view->orderBy = $orderBy; + die($this->display()); + } + + /** + * Export the stories of release to HTML. + * + * @param string $type story | bug + * @access public + * @return void + */ + public function export($type) + { + if(!empty($_POST)) + { + if($type == 'story') + { + $this->loadModel('story'); + + $stories = $this->dao->select('id, title')->from(TABLE_STORY)->where($this->session->storyQueryCondition) + ->beginIF($this->session->storyOrderBy != false)->orderBy($this->session->storyOrderBy)->fi() + ->fetchAll('id'); + + foreach($stories as $story) + { + $story->title = "id") . "' target='_blank'>$story->title"; + } + + $this->post->set('fields', array('id' => $this->lang->story->id, 'title' => $this->lang->story->title)); + $this->post->set('rows', $stories); + $this->fetch('file', 'export2HTML', $_POST); + } + else if($type == 'bug') + { + $this->loadModel('bug'); + + $bugs = $this->dao->select('id, title')->from(TABLE_BUG)->where($this->session->bugQueryCondition) + ->beginIF($this->session->bugOrderBy != false)->orderBy($this->session->bugOrderBy)->fi() + ->fetchAll('id'); + + foreach($bugs as $bug) + { + $bug->title = "id") . "' target='_blank'>$bug->title"; + } + + $this->post->set('fields', array('id' => $this->lang->bug->id, 'title' => $this->lang->bug->title)); + $this->post->set('rows', $bugs); + $this->fetch('file', 'export2HTML', $_POST); + } + } + + $this->display(); + } +} diff --git a/trunk/module/release/css/common.css b/trunk/module/release/css/common.css new file mode 100755 index 0000000000..e8abbee4f8 --- /dev/null +++ b/trunk/module/release/css/common.css @@ -0,0 +1,3 @@ +.mainTable { width:100%; border:none} +.headTable { width:100%; height:30px; margin:0px;} +.contentDiv { height:195px; overflow-y:auto} diff --git a/trunk/module/release/js/common.js b/trunk/module/release/js/common.js new file mode 100644 index 0000000000..0c5230f1b5 --- /dev/null +++ b/trunk/module/release/js/common.js @@ -0,0 +1,13 @@ +function loadStoriesAndBugs(buildID,productID) +{ + link = createLink('release', 'ajaxGetStoriesAndBugs', 'buildID=' + buildID + '&productID=' + productID); + $('#linkStoriesAndBugs').load(link, function() + { + $("a.preview").colorbox({width:1000, height:600, iframe:true, transition:'elastic', speed:350, scrolling:true}); + }) +} + +$(document).ready(function() +{ + $("a.preview").colorbox({width:1000, height:600, iframe:true, transition:'elastic', speed:350, scrolling:true}); +}) diff --git a/trunk/module/release/lang/en.php b/trunk/module/release/lang/en.php new file mode 100644 index 0000000000..5679f4e824 --- /dev/null +++ b/trunk/module/release/lang/en.php @@ -0,0 +1,37 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->release->common = 'Release'; +$lang->release->create = "Create"; +$lang->release->edit = "Edit"; +$lang->release->delete = "Delete"; +$lang->release->view = "Info"; +$lang->release->browse = "Browse"; + +$lang->release->confirmDelete = "Are sure to delete this release?"; + +$lang->release->basicInfo ='Basic Info'; + +$lang->release->id = 'ID'; +$lang->release->product = 'Product'; +$lang->release->build = 'Build'; +$lang->release->name = 'Name'; +$lang->release->date = 'Date'; +$lang->release->desc = 'Desc'; +$lang->release->linkStoriesAndBugs = 'Stories and bugs'; +$lang->release->linkStories = 'Stories'; +$lang->release->linkBugs = 'Bugs'; +$lang->release->stories = 'Linked stories'; +$lang->release->bugs = 'Linked bugs'; +$lang->release->ajaxGetStoriesAndBugs = 'API: Get storeis and bugs'; +$lang->release->finishStories = 'The total demand for a complete %s'; +$lang->release->resolvedBugs = 'The total solution of bug%s'; +$lang->release->export = 'Export as HTML'; diff --git a/trunk/module/release/lang/zh-cn.php b/trunk/module/release/lang/zh-cn.php new file mode 100644 index 0000000000..a284b17d29 --- /dev/null +++ b/trunk/module/release/lang/zh-cn.php @@ -0,0 +1,37 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->release->common = '发布'; +$lang->release->create = "创建发布"; +$lang->release->edit = "编辑发布"; +$lang->release->delete = "删除发布"; +$lang->release->view = "发布详情"; +$lang->release->browse = "浏览发布"; + +$lang->release->confirmDelete = "您确认删除该release吗?"; + +$lang->release->basicInfo ='基本信息'; + +$lang->release->id = 'ID'; +$lang->release->product = '产品'; +$lang->release->build = '版本'; +$lang->release->name = '发布名称'; +$lang->release->date = '发布日期'; +$lang->release->desc = '描述'; +$lang->release->linkStoriesAndBugs = '需求和Bug'; +$lang->release->linkStories = '相关需求'; +$lang->release->linkBugs = '相关Bug'; +$lang->release->stories = '已关联需求'; +$lang->release->bugs = '已关联Bugs'; +$lang->release->ajaxGetStoriesAndBugs = '接口:获得需求和Bug'; +$lang->release->finishStories = '本次共完成需求%s个'; +$lang->release->resolvedBugs = '本次共解决Bug%s个'; +$lang->release->export = '导出HTML'; diff --git a/trunk/module/release/lang/zh-tw.php b/trunk/module/release/lang/zh-tw.php new file mode 100644 index 0000000000..258b9cafa9 --- /dev/null +++ b/trunk/module/release/lang/zh-tw.php @@ -0,0 +1,37 @@ + + * @package release + * @version $Id: zh-tw.php 3301 2012-07-03 01:25:29Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +$lang->release->common = '發佈'; +$lang->release->create = "創建發佈"; +$lang->release->edit = "編輯發佈"; +$lang->release->delete = "刪除發佈"; +$lang->release->view = "發佈詳情"; +$lang->release->browse = "瀏覽發佈"; + +$lang->release->confirmDelete = "您確認刪除該release嗎?"; + +$lang->release->basicInfo ='基本信息'; + +$lang->release->id = 'ID'; +$lang->release->product = '產品'; +$lang->release->build = '版本'; +$lang->release->name = '發佈名稱'; +$lang->release->date = '發佈日期'; +$lang->release->desc = '描述'; +$lang->release->linkStoriesAndBugs = '需求和Bug'; +$lang->release->linkStories = '相關需求'; +$lang->release->linkBugs = '相關Bug'; +$lang->release->stories = '已關聯需求'; +$lang->release->bugs = '已關聯Bugs'; +$lang->release->ajaxGetStoriesAndBugs = '介面:獲得需求和Bug'; +$lang->release->finishStories = '本次共完成需求%s個'; +$lang->release->resolvedBugs = '本次共解決Bug%s個'; +$lang->release->export = '導出HTML'; diff --git a/trunk/module/release/model.php b/trunk/module/release/model.php new file mode 100644 index 0000000000..928324ca73 --- /dev/null +++ b/trunk/module/release/model.php @@ -0,0 +1,131 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +?> +dao->select('t1.*, t2.name as buildName, t3.name as productName') + ->from(TABLE_RELEASE)->alias('t1') + ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->where('t1.id')->eq((int)$releaseID) + ->orderBy('t1.id DESC') + ->fetch(); + if($setImgSize) $release->desc = $this->loadModel('file')->setImgSize($release->desc); + return $release; + } + + /** + * Get list of releases. + * + * @param int $productID + * @access public + * @return array + */ + public function getList($productID) + { + return $this->dao->select('t1.*, t2.name as productName, t3.name as buildName') + ->from(TABLE_RELEASE)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') + ->leftJoin(TABLE_BUILD)->alias('t3')->on('t1.build = t3.id') + ->where('t1.product')->eq((int)$productID) + ->andWhere('t1.deleted')->eq(0) + ->orderBy('t1.date DESC') + ->fetchAll(); + } + + /** + * Get release builds from product. + * + * @param int $productID + * @access public + * @return void + */ + public function getReleaseBuilds($productID) + { + $releases = $this->dao->select('build')->from(TABLE_RELEASE)->where('deleted')->eq(0)->andWhere('product')->eq($productID)->fetchAll('build'); + return array_keys($releases); + } + + /** + * Create a release. + * + * @param int $productID + * @access public + * @return int + */ + public function create($productID) + { + if($this->post->build == false) + { + $build = fixer::input('post') + ->stripTags('name') + ->add('product', (int)$productID) + ->add('builder', $this->app->user->account) + ->remove('build') + ->get(); + $this->dao->insert(TABLE_BUILD)->data($build)->autoCheck()->check('name','unique')->exec(); + $buildID = $this->dao->lastInsertID(); + } + + $release = fixer::input('post') + ->stripTags('name') + ->add('product', (int)$productID) + ->join('stories', ',') + ->join('bugs', ',') + ->remove('allchecker') + ->setIF($this->post->build ==false, 'build', $buildID) + ->get(); + + $this->dao->insert(TABLE_RELEASE)->data($release)->autoCheck()->batchCheck($this->config->release->create->requiredFields, 'notempty')->check('name','unique')->exec(); + $releaseID = $this->dao->lastInsertID(); + $this->dao->update(TABLE_STORY)->set('stage')->eq('released')->where('id')->in($release->stories)->exec(); + if(!dao::isError()) return $releaseID; + } + + /** + * Update a release. + * + * @param int $releaseID + * @access public + * @return void + */ + public function update($releaseID) + { + $oldRelease = $this->getByID($releaseID); + $release = fixer::input('post') + ->stripTags('name') + ->setDefault('stories', '') + ->setDefault('bugs', '') + ->join('stories', ',') + ->join('bugs', ',') + ->get(); + $this->dao->update(TABLE_RELEASE)->data($release) + ->autoCheck() + ->batchCheck($this->config->release->edit->requiredFields, 'notempty') + ->check('name','unique', "id != $releaseID") + ->where('id')->eq((int)$releaseID) + ->exec(); + $this->dao->update(TABLE_STORY)->set('stage')->eq('released')->where('id')->in($release->stories)->exec(); + if(!dao::isError()) return common::createChanges($oldRelease, $release); + } +} diff --git a/trunk/module/release/view/ajaxgetstoriesandbugs.html.php b/trunk/module/release/view/ajaxgetstoriesandbugs.html.php new file mode 100644 index 0000000000..9d7cfbf9ca --- /dev/null +++ b/trunk/module/release/view/ajaxgetstoriesandbugs.html.php @@ -0,0 +1,78 @@ +release->linkStoriesAndBugs;?> + +
      + +
      + + + + + + + +
      + + + + + + + + +
      release->linkStories;?>
      idAB;?>story->title;?>statusAB;?>story->stageAB;?>
      +
      +
      + + $story):?> + createLink('story', 'view', "storyID=$storyID");?> + + + + + + + +
      + stage == 'developed' or $story->status == 'closed') echo 'checked';?>> id);?> + title, '', "class='preview'");?>story->statusList[$story->status];?>story->stageList[$story->stage];?>
      +
      +
      +
      + +
      + + + + + + + +
      + + + + + + + +
      release->linkBugs;?>
      idAB;?>bug->title;?>bug->status;?>
      +
      +
      + + + createLink('bug', 'view', "bugID=$bug->id");?> + + + + + + +
      + status == 'closed' or $bug->status == 'resolved') echo "checked"; ?>> id);?> + title, '', "class='preview'");?>bug->statusList[$bug->status];?>
      +
      +
      +
      + +
      + diff --git a/trunk/module/release/view/browse.html.php b/trunk/module/release/view/browse.html.php new file mode 100644 index 0000000000..fbb8459b7e --- /dev/null +++ b/trunk/module/release/view/browse.html.php @@ -0,0 +1,46 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      release->browse;?>
      +
      id", $lang->release->create);?>
      +
      release->id;?>release->name;?>release->build;?>release->date;?>actions;?>
      id;?>id"), $release->name);?>buildName;?>date;?> + id", $lang->edit); + common::printLink('release', 'delete', "release=$release->id", $lang->delete, 'hiddenwin'); + ?> +
      + diff --git a/trunk/module/release/view/create.html.php b/trunk/module/release/view/create.html.php new file mode 100644 index 0000000000..9a5ae73be6 --- /dev/null +++ b/trunk/module/release/view/create.html.php @@ -0,0 +1,45 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + +
      release->create;?>
      release->name;?>
      release->build;?> + build->notice; + ?> +
      release->date;?>
      release->desc;?>
      +
      + diff --git a/trunk/module/release/view/edit.html.php b/trunk/module/release/view/edit.html.php new file mode 100644 index 0000000000..5daacd1e18 --- /dev/null +++ b/trunk/module/release/view/edit.html.php @@ -0,0 +1,118 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      release->edit;?>
      release->name;?>name, "class='text-3'");?>
      release->build;?>build, "class='select-3' onchange=loadStoriesAndBugs(this.value,$release->product)"); ?>
      release->date;?>date, "class='text-3 date'");?>
      release->linkStoriesAndBugs;?> +
      + +
      + + + + + + + +
      + + + + + + + + +
      release->linkStories;?>
      idAB;?>story->title;?>statusAB;?>story->stageAB;?>
      +
      +
      + + $story):?> + createLink('story', 'view', "storyID=$story->id"); + ?> + + + + + + + +
      stories, $story->id) !== false) echo 'checked';?>> id);?>title, '', "class='preview'");?>story->statusList[$story->status];?>story->stageList[$story->stage];?>
      +
      +
      +
      + +
      + + + + + + + +
      + + + + + + + +
      release->linkBugs;?>
      idAB;?>bug->title;?>bug->status;?>
      +
      +
      + + + createLink('bug', 'view', "bugID=$bug->id");?> + + + + + + +
      bugs, $bug->id) !== false) echo 'checked';?>> id);?>title, '', "class='preview'");?>bug->statusList[$bug->status];?>
      +
      +
      +
      + +
      +
      release->desc;?>desc), "rows='20' class='area-1'");?>
      product);?>
      +
      + diff --git a/trunk/module/release/view/export.html.php b/trunk/module/release/view/export.html.php new file mode 100755 index 0000000000..20c1cb2296 --- /dev/null +++ b/trunk/module/release/view/export.html.php @@ -0,0 +1,49 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +

      +
      + + + + + +
      export;?>
      + setFileName;?> + + +
      +
      + diff --git a/trunk/module/release/view/view.html.php b/trunk/module/release/view/view.html.php new file mode 100644 index 0000000000..6d5aa4f6eb --- /dev/null +++ b/trunk/module/release/view/view.html.php @@ -0,0 +1,117 @@ + + * @package release + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
      RELEASE #id . ' ' . $release->name;?>
      +
      + release->desc;?> +
      desc;?>
      +
      + +
      + session->releaseList ? $this->session->releaseList : inlink('browse', "productID=$release->product"); + if(!$release->deleted) + { + common::printLink('release', 'edit', "releaseID=$release->id", $lang->edit); + common::printLink('release', 'delete', "releaseID=$release->id", $lang->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      + + + + + + + + + + + + $story):?> + createLink('story', 'view', "storyID=$story->id");?> + + + + + + + + + + + +
      + release->stories;?> +
      release->export, '', "class='export'");?>
      +
      idAB;?>priAB;?>story->title;?>openedByAB;?>story->estimateAB;?>statusAB;?>story->stageAB;?>
      id);?>story->priList[$story->pri];?>title, '', "class='preview'");?>openedBy];?>estimate;?>story->statusList[$story->status];?>story->stageList[$story->stage];?>
      release->finishStories, count($stories));?>
      + + + + + + + + + + + + + createLink('bug', 'view', "bugID=$bug->id");?> + + + + + + + + + + + +
      + release->bugs;?> +
      release->export, '', "class='export'");?>
      +
      idAB;?>bug->title;?>bug->status;?>openedByAB;?>bug->openedDateAB;?>bug->resolvedByAB;?>bug->resolvedDateAB;?>
      id);?>title, '', "class='preview'");?>bug->statusList[$bug->status];?>openedBy];?>openedDate, 5, 11)?>resolvedBy];?>resolvedDate, 5, 11)?>
      release->resolvedBugs, count($bugs));?>
      +
      +
      + release->basicInfo?> + + + + + + + + + + + + + + + + + +
      release->product;?>productName;?>
      release->name;?>'>name;?>
      release->build;?>buildName;?>
      release->date;?>date;?>
      +
      +
      + diff --git a/trunk/module/report/control.php b/trunk/module/report/control.php new file mode 100644 index 0000000000..7bd127c5c4 --- /dev/null +++ b/trunk/module/report/control.php @@ -0,0 +1,122 @@ + + * @package report + * @version $Id$ + * @link http://www.zentao.net + */ +class report extends control +{ + /** + * The index of report, goto project deviation. + * + * @access public + * @return void + */ + public function index() + { + $this->locate(inlink('productinfo')); + } + + /** + * Project deviation report. + * + * @access public + * @return void + */ + public function projectDeviation() + { + $this->view->header->title = $this->lang->report->projectDeviation; + $this->view->projects = $this->report->getProjects(); + $this->view->submenu = 'project'; + $this->display(); + } + + /** + * Product information report. + * + * @access public + * @return void + */ + public function productInfo() + { + $this->app->loadLang('product'); + $this->app->loadLang('productplan'); + $this->app->loadLang('story'); + $this->view->header->title = $this->lang->report->productInfo; + $this->view->products = $this->report->getProducts(); + $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed'); + $this->view->submenu = 'product'; + $this->display(); + } + + /** + * Bug summary report. + * + * @param int $begin + * @param int $end + * @access public + * @return void + */ + public function bugSummary($begin = 0, $end = 0) + { + $this->app->loadLang('bug'); + if($begin == 0) + { + $begin = date('Y-m-d', strtotime('last month')); + } + else + { + $begin = date('Y-m-d', strtotime($begin)); + } + if($end == 0) + { + $end = date('Y-m-d', strtotime('now')); + } + else + { + $end = date('Y-m-d', strtotime($end)); + } + $this->view->header->title = $this->lang->report->bugSummary; + $this->view->begin = $begin; + $this->view->end = $end; + $this->view->bugs = $this->report->getBugs($begin, $end); + $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed|nodeleted'); + $this->view->submenu = 'test'; + $this->display(); + } + + /** + * Bug assign report. + * + * @access public + * @return void + */ + public function bugAssign() + { + $this->view->header->title = $this->lang->report->bugAssign; + $this->view->submenu = 'test'; + $this->view->assigns = $this->report->getBugAssign(); + $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed|nodeleted'); + $this->display(); + } + + /** + * Workload report. + * + * @access public + * @return void + */ + public function workload() + { + $this->view->header->title = $this->lang->report->workload; + $this->view->workload = $this->report->getWorkload(); + $this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed|nodeleted'); + $this->view->submenu = 'staff'; + $this->display(); + } +} diff --git a/trunk/module/report/css/common.css b/trunk/module/report/css/common.css new file mode 100644 index 0000000000..a7dc66d138 --- /dev/null +++ b/trunk/module/report/css/common.css @@ -0,0 +1,23 @@ +.choose-date span, .dp-choose-date{ display:block; float:left; margin:0 10px;} +.dp-applied{float:left;} +.choose-date span{line-height:20px;} +.date{width:80px;} +ul#report-list{margin:0px;} +ul li{list-style:none;} +#product td, tr, th{ border:1px solid #E4E4E4;} +#workload td, tr, th{ border:1px solid #E4E4E4;} +#bug td, tr, th{ border:1px solid #E4E4E4;} +#product p{margin:2px 0;} + +.proversion {text-align:center; margin-top:10px;} +.proversion a{color:red;} +td .deviation{padding-left:20px; text-align:left;} +.up{color:red; padding-right:2px;} +.down{color:green; padding-right:2px;} +.zero{color:#66CD00;} +.u50{color:#ED1C24;} +.u30{color:#F37021;} +.u10{color:#FAA61A;} +.u0{color:#FFCB05;} +.d0{color:#76B043;} +.d20{color:#33A52E;} diff --git a/trunk/module/report/js/bugsummary.js b/trunk/module/report/js/bugsummary.js new file mode 100644 index 0000000000..29d56a1b3b --- /dev/null +++ b/trunk/module/report/js/bugsummary.js @@ -0,0 +1,23 @@ +function changeDate(begin, end) +{ + if(begin.indexOf('-') != -1) + { + var beginarray = begin.split("-"); + var begin = ''; + for(i=0 ; i < beginarray.length ; i++) + { + begin = begin + beginarray[i]; + } + } + if(end.indexOf('-') != -1) + { + var endarray = end.split("-"); + var end = ''; + for(i=0 ; i < endarray.length ; i++) + { + end = end + endarray[i]; + } + } + link = createLink('report', 'bugsummary', 'begin=' + begin + '&end=' + end); + location.href=link; +} diff --git a/trunk/module/report/js/workload.js b/trunk/module/report/js/workload.js new file mode 100644 index 0000000000..6ecdb89782 --- /dev/null +++ b/trunk/module/report/js/workload.js @@ -0,0 +1,23 @@ +function changeDate(begin, end) +{ + if(begin.indexOf('-') != -1) + { + var beginarray = begin.split("-"); + var begin = ''; + for(i=0 ; i < beginarray.length ; i++) + { + begin = begin + beginarray[i]; + } + } + if(end.indexOf('-') != -1) + { + var endarray = end.split("-"); + var end = ''; + for(i=0 ; i < endarray.length ; i++) + { + end = end + endarray[i]; + } + } + link = createLink('report', 'workload', 'begin=' + begin + '&end=' + end); + location.href=link; +} diff --git a/trunk/module/report/lang/en.php b/trunk/module/report/lang/en.php new file mode 100644 index 0000000000..117b3107c1 --- /dev/null +++ b/trunk/module/report/lang/en.php @@ -0,0 +1,72 @@ + + * @package report + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->report->common = 'Report'; +$lang->report->index = 'Report index'; +$lang->report->list = 'Report list'; +$lang->report->item = 'Item'; +$lang->report->value = 'Value'; +$lang->report->percent = 'Percent'; +$lang->report->undefined = 'Undefined'; +$lang->report->time = 'Time'; + +$lang->report->colors[] = 'AFD8F8'; +$lang->report->colors[] = 'F6BD0F'; +$lang->report->colors[] = '8BBA00'; +$lang->report->colors[] = 'FF8E46'; +$lang->report->colors[] = '008E8E'; +$lang->report->colors[] = 'D64646'; +$lang->report->colors[] = '8E468E'; +$lang->report->colors[] = '588526'; +$lang->report->colors[] = 'B3AA00'; +$lang->report->colors[] = '008ED6'; +$lang->report->colors[] = '9D080D'; +$lang->report->colors[] = 'A186BE'; + +$lang->report->singleColor[] = 'F6BD0F'; + +$lang->report->projectDeviation = 'Project deviation'; +$lang->report->productInfo = 'Product information'; +$lang->report->bugSummary = 'Bug summary'; +$lang->report->bugAssign = 'Bug assign'; +$lang->report->workload = 'Workload'; + +$lang->reportList->project->lists[10] = 'Project deviation|report|projectdeviation'; +$lang->reportList->product->lists[10] = 'Product information|report|productinfo'; +$lang->reportList->test->lists[10] = 'Bug summary|report|bugsummary'; +$lang->reportList->test->lists[13] = 'Bug assign|report|bugassign'; +$lang->reportList->staff->lists[10] = 'Workload|report|workload'; + +$lang->report->id = 'ID'; +$lang->report->project = 'Project'; +$lang->report->product = 'Product'; +$lang->report->user = 'Username'; +$lang->report->bug = 'Bug'; +$lang->report->task = 'Tasks'; +$lang->report->estimate = 'Estimate'; +$lang->report->consumed = 'Consumed'; +$lang->report->remain = 'Remain'; +$lang->report->manhour = 'Manhour'; +$lang->report->deviation = 'Deviation'; +$lang->report->deviationRate = 'Deviation rate'; +$lang->report->stories = 'Stories'; +$lang->report->bugs = 'Bugs'; +$lang->report->devConsumed = 'Develop'; +$lang->report->testConsumed = 'Test'; +$lang->report->devTestRate = 'Dev/Test'; +$lang->report->details = 'Details'; +$lang->report->total = 'Total'; +$lang->report->to = 'to'; +$lang->report->taskTotal = "Task Total"; +$lang->report->manhourTotal = "Manhour Total"; +$lang->report->bugTotal = "%s Bugs"; + +$lang->report->proVersion = 'Try pro version for more!'; diff --git a/trunk/module/report/lang/zh-cn.php b/trunk/module/report/lang/zh-cn.php new file mode 100644 index 0000000000..df199dfcde --- /dev/null +++ b/trunk/module/report/lang/zh-cn.php @@ -0,0 +1,72 @@ + + * @package report + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->report->common = '统计视图'; +$lang->report->index = '统计首页'; +$lang->report->list = '统计报表'; +$lang->report->item = '条目'; +$lang->report->value = '值'; +$lang->report->percent = '百分比'; +$lang->report->undefined = '未设定'; +$lang->report->time = '时间'; + +$lang->report->colors[] = 'AFD8F8'; +$lang->report->colors[] = 'F6BD0F'; +$lang->report->colors[] = '8BBA00'; +$lang->report->colors[] = 'FF8E46'; +$lang->report->colors[] = '008E8E'; +$lang->report->colors[] = 'D64646'; +$lang->report->colors[] = '8E468E'; +$lang->report->colors[] = '588526'; +$lang->report->colors[] = 'B3AA00'; +$lang->report->colors[] = '008ED6'; +$lang->report->colors[] = '9D080D'; +$lang->report->colors[] = 'A186BE'; + +$lang->report->singleColor[] = 'F6BD0F'; + +$lang->report->projectDeviation = '项目偏差报表'; +$lang->report->productInfo = '产品信息统计表'; +$lang->report->bugSummary = 'Bug汇总表'; +$lang->report->bugAssign = 'Bug指派表'; +$lang->report->workload = '员工负载表'; + +$lang->reportList->project->lists[10] = '项目偏差报表|report|projectdeviation'; +$lang->reportList->product->lists[10] = '产品信息统计表|report|productinfo'; +$lang->reportList->test->lists[10] = 'Bug汇总表|report|bugsummary'; +$lang->reportList->test->lists[13] = 'Bug指派表|report|bugassign'; +$lang->reportList->staff->lists[10] = '员工负载表|report|workload'; + +$lang->report->id = '编号'; +$lang->report->project = '项目'; +$lang->report->product = '产品'; +$lang->report->user = '姓名'; +$lang->report->bug = 'Bug'; +$lang->report->task = '任务数'; +$lang->report->estimate = '总预计'; +$lang->report->consumed = '总消耗'; +$lang->report->remain = '剩余工时'; +$lang->report->manhour = '工时'; +$lang->report->deviation = '偏差'; +$lang->report->deviationRate = '偏差率'; +$lang->report->stories = '需求数'; +$lang->report->bugs = 'Bug数'; +$lang->report->devConsumed = '开发时间'; +$lang->report->testConsumed = '测试时间'; +$lang->report->devTestRate = '开发/测试'; +$lang->report->details = '详情'; +$lang->report->total = '总计'; +$lang->report->to = '至'; +$lang->report->taskTotal = "总任务数"; +$lang->report->manhourTotal = "总工时"; +$lang->report->bugTotal = "共%s个Bug"; + +$lang->report->proVersion = '更多精彩,尽在专业版!'; diff --git a/trunk/module/report/lang/zh-tw.php b/trunk/module/report/lang/zh-tw.php new file mode 100644 index 0000000000..5027ac1274 --- /dev/null +++ b/trunk/module/report/lang/zh-tw.php @@ -0,0 +1,72 @@ + + * @package report + * @version $Id: zh-tw.php 3381 2012-08-09 09:25:12Z zhujinyonging@gmail.com $ + * @link http://www.zentao.net + */ +$lang->report->common = '統計視圖'; +$lang->report->index = '統計首頁'; +$lang->report->list = '統計報表'; +$lang->report->item = '條目'; +$lang->report->value = '值'; +$lang->report->percent = '百分比'; +$lang->report->undefined = '未設定'; +$lang->report->time = '時間'; + +$lang->report->colors[] = 'AFD8F8'; +$lang->report->colors[] = 'F6BD0F'; +$lang->report->colors[] = '8BBA00'; +$lang->report->colors[] = 'FF8E46'; +$lang->report->colors[] = '008E8E'; +$lang->report->colors[] = 'D64646'; +$lang->report->colors[] = '8E468E'; +$lang->report->colors[] = '588526'; +$lang->report->colors[] = 'B3AA00'; +$lang->report->colors[] = '008ED6'; +$lang->report->colors[] = '9D080D'; +$lang->report->colors[] = 'A186BE'; + +$lang->report->singleColor[] = 'F6BD0F'; + +$lang->report->projectDeviation = '項目偏差報表'; +$lang->report->productInfo = '產品信息統計表'; +$lang->report->bugSummary = 'Bug彙總表'; +$lang->report->bugAssign = 'Bug指派表'; +$lang->report->workload = '員工負載表'; + +$lang->reportList->project->lists[10] = '項目偏差報表|report|projectdeviation'; +$lang->reportList->product->lists[10] = '產品信息統計表|report|productinfo'; +$lang->reportList->test->lists[10] = 'Bug彙總表|report|bugsummary'; +$lang->reportList->test->lists[13] = 'Bug指派表|report|bugassign'; +$lang->reportList->staff->lists[10] = '員工負載表|report|workload'; + +$lang->report->id = '編號'; +$lang->report->project = '項目'; +$lang->report->product = '產品'; +$lang->report->user = '姓名'; +$lang->report->bug = 'Bug'; +$lang->report->task = '任務數'; +$lang->report->estimate = '總預計'; +$lang->report->consumed = '總消耗'; +$lang->report->remain = '剩餘工時'; +$lang->report->manhour = '工時'; +$lang->report->deviation = '偏差'; +$lang->report->deviationRate = '偏差率'; +$lang->report->stories = '需求數'; +$lang->report->bugs = 'Bug數'; +$lang->report->devConsumed = '開發時間'; +$lang->report->testConsumed = '測試時間'; +$lang->report->devTestRate = '開發/測試'; +$lang->report->details = '詳情'; +$lang->report->total = '總計'; +$lang->report->to = '至'; +$lang->report->taskTotal = "總任務數"; +$lang->report->manhourTotal = "總工時"; +$lang->report->bugTotal = "共%s個Bug"; + +$lang->report->proVersion = '更多精彩,盡在專業版!'; diff --git a/trunk/module/report/model.php b/trunk/module/report/model.php new file mode 100644 index 0000000000..d6146244eb --- /dev/null +++ b/trunk/module/report/model.php @@ -0,0 +1,431 @@ + + * @package report + * @version $Id$ + * @link http://www.zentao.net + */ +?> +app->getWebRoot() . 'fusioncharts/'; + $swfFile = "fcf_$swf.swf"; + return << + + + + + + +EOT; + } + + /** + * Create the js code of chart. + * + * @param string $swf the swf type + * @param string $dataURL the date url + * @param int $width + * @param int $height + * @access public + * @return string + */ + public function createJSChart($swf, $dataXML, $width = 'auto', $height = 500) + { + $jsRoot = $this->app->getWebRoot() . 'js/'; + static $count = 0; + $count ++; + $chartRoot = $this->app->getWebRoot() . 'fusioncharts/'; + $swfFile = "fcf_$swf.swf"; + $divID = "chart{$count}div"; + $chartID = "chart{$count}"; + + $js = ''; + if($count == 1) $js = ""; + return << + +EOT; + } + + public function createJSChartFlot($projectName, $dataJSON, $count, $width = 'auto', $height = 500) + { + $this->app->loadLang('project'); + $jsRoot = $this->app->getWebRoot() . 'js/'; + $width = $width . 'px'; + $height = $height . 'px'; +return << + +

      $projectName {$this->lang->project->burn}

      +
      + +EOT; + } + + /** + * Create xml data of single charts. + * + * @param array $sets + * @param array $chartOptions + * @param array $colors + * @access public + * @return string the xml data. + */ + public function createSingleXML($sets, $chartOptions = array(), $colors = array()) + { + $data = pack("CCC", 0xef, 0xbb, 0xbf); // utf-8 bom. + $data .=""; + + $data .= ' $value) $data .= " $key='$value'"; + $data .= ">"; + + if(empty($colors)) $colors = $this->lang->report->colors; + $colorCount = count($colors); + $i = 0; + foreach($sets as $set) + { + if($i == $colorCount) $i = 0; + $color = $colors[$i]; + $i ++; + $data .= ""; + } + $data .= ""; + return $data; + } + + public function createSingleJSON($sets) + { + $data = '['; + foreach($sets as $set) + { + $data .= " [$set->name, $set->value],"; + } + $data = rtrim($data, ','); + $data .= ']'; + return $data; + } + + /** + * Create the js code to render chart. + * + * @param int $chartCount + * @access public + * @return string + */ + public function renderJsCharts($chartCount) + { + $js = ''; + return $js; + } + + /** + * Compute percent of every item. + * + * @param array $datas + * @access public + * @return array + */ + public function computePercent($datas) + { + $sum = 0; + foreach($datas as $data) $sum += $data->value; + foreach($datas as $data) $data->percent = round($data->value / $sum, 2); + return $datas; + } + + /** + * Get projects. + * + * @access public + * @return void + */ + public function getProjects() + { + $projects = array(); + + $tasks = $this->dao->select('t1.*') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.status')->ne('cancel') + ->andWhere('t1.deleted')->eq(0) + ->andWhere('t2.deleted')->eq(0) + ->fetchAll(); + foreach($tasks as $task) + { + $projects[$task->project]->estimate = isset($projects[$task->project]->estimate) ? $projects[$task->project]->estimate + $task->estimate : $task->estimate; + $projects[$task->project]->consumed = isset($projects[$task->project]->consumed) ? $projects[$task->project]->consumed + $task->consumed : $task->consumed; + $projects[$task->project]->tasks = isset($projects[$task->project]->tasks) ? $projects[$task->project]->tasks + 1 : 1; + if($task->type == 'devel') $projects[$task->project]->devConsumed = isset($projects[$task->project]->devConsumed) ? $projects[$task->project]->devConsumed + $task->consumed : $task->consumed; + if($task->type == 'test') $projects[$task->project]->testConsumed = isset($projects[$task->project]->testConsumed) ? $projects[$task->project]->testConsumed + $task->consumed : $task->consumed; + } + + $bugs = $this->dao->select('t1.project') + ->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.deleted')->eq(0) + ->andWhere('t2.deleted')->eq(0) + ->fetchAll(); + foreach($bugs as $bug) + { + if($bug->project) + { + $projects[$bug->project]->bugs = isset($projects[$bug->project]->bugs) ? $projects[$bug->project]->bugs + 1 : 1; + } + } + + $stories = $this->dao->select('t1.project') + ->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->leftJoin(TABLE_STORY)->alias('t3') + ->on('t1.story = t3.id') + ->where('t2.deleted')->eq(0) + ->andWhere('t3.deleted')->eq(0) + ->fetchAll(); + foreach($stories as $story) + { + $projects[$story->project]->stories = isset($projects[$story->project]->stories) ? $projects[$story->project]->stories + 1 : 1; + } + + $projectList = $this->dao->select('id, name')->from(TABLE_PROJECT)->fetchAll(); + $projectPairs = array(); + foreach($projectList as $project) + { + $projectPairs[$project->id] = $project->name; + } + foreach($projects as $id => $project) + { + if(!isset($projectPairs[$id])) + { + unset($projects[$id]); + continue; + } + if(!isset($project->stories)) $projects[$id]->stories = 0; + if(!isset($project->bugs)) $projects[$id]->bugs = 0; + if(!isset($project->devConsumed)) $projects[$id]->devConsumed = 0; + if(!isset($project->testConsumed)) $projects[$id]->testConsumed = 0; + if(!isset($project->consumed)) $projects[$id]->consumed = 0; + if(!isset($project->estimate)) $projects[$id]->estimate = 0; + $projects[$id]->name = $projectPairs[$id]; + } + return $projects; + } + + /** + * Get products. + * + * @access public + * @return array + */ + public function getProducts() + { + $products = $this->dao->select('id, code, name, PO')->from(TABLE_PRODUCT)->where('deleted')->eq(0)->fetchAll('id'); + $plans = $this->dao->select('*')->from(TABLE_PRODUCTPLAN)->where('deleted')->eq(0)->andWhere('product')->in(array_keys($products))->fetchAll('id'); + $planStories = $this->dao->select('plan, id, status')->from(TABLE_STORY)->where('deleted')->eq(0)->andWhere('plan')->in(array_keys($plans))->fetchGroup('plan', 'id'); + foreach($plans as $plan) + { + $products[$plan->product]->plans[$plan->id]->title = $plan->title; + $products[$plan->product]->plans[$plan->id]->desc = $plan->desc; + $products[$plan->product]->plans[$plan->id]->begin = $plan->begin; + $products[$plan->product]->plans[$plan->id]->end = $plan->end; + } + foreach($planStories as $planID => $stories) + { + foreach($stories as $story) + { + $plan = $plans[$story->plan]; + $products[$plan->product]->plans[$story->plan]->status[$story->status] = isset($products[$plan->product]->plans[$story->plan]->status[$story->status]) ? $products[$plan->product]->plans[$story->plan]->status[$story->status] + 1 : 1; + } + } + return $products; + } + + /** + * Get bugs + * + * @param int $begin + * @param int $end + * @access public + * @return array + */ + public function getBugs($begin, $end) + { + $end = date('Ymd', strtotime("$end +1 day")); + $bugs = $this->dao->select('id, resolution, openedBy')->from(TABLE_BUG) + ->where('deleted')->eq(0) + ->andWhere('openedDate')->ge($begin) + ->andWhere('openedDate')->le($end) + ->fetchAll(); + $bugSummary = array(); + foreach($bugs as $bug) + { + $bugSummary[$bug->openedBy][$bug->resolution] = empty($bugSummary[$bug->openedBy][$bug->resolution]) ? 1 : $bugSummary[$bug->openedBy][$bug->resolution] + 1; + $bugSummary[$bug->openedBy]['all'] = empty($bugSummary[$bug->openedBy]['all']) ? 1 : $bugSummary[$bug->openedBy]['all'] + 1; + } + return $bugSummary; + } + + /** + * Get workload. + * + * @access public + * @return array + */ + public function getWorkload() + { + $tasks = $this->dao->select('t1.*, t2.name as projectName') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.status')->notin('cancel, closed, done') + ->andWhere('t2.deleted')->eq(0) + ->fetchGroup('assignedTo'); + $workload = array(); + foreach($tasks as $user => $userTasks) + { + if($user) + { + foreach($userTasks as $task) + { + $workload[$user]['task'][$task->projectName]['count'] = isset($workload[$user]['task'][$task->projectName]['count']) ? $workload[$user]['task'][$task->projectName]['count'] + 1 : 1; + $workload[$user]['task'][$task->projectName]['manhour'] = isset($workload[$user]['task'][$task->projectName]['manhour']) ? $workload[$user]['task'][$task->projectName]['manhour'] + $task->left : $task->left; + $workload[$user]['total']['count'] = isset($workload[$user]['total']['count']) ? $workload[$user]['total']['count'] + 1 : 1; + $workload[$user]['total']['manhour'] = isset($workload[$user]['total']['manhour']) ? $workload[$user]['total']['manhour'] + $task->left : $task->left; + } + } + } + unset($workload['closed']); + return $workload; + } + + /** + * Get bug assign. + * + * @access public + * @return array + */ + public function getBugAssign() + { + $bugs = $this->dao->select('t1.*, t2.name as productName') + ->from(TABLE_BUG)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.status')->eq('active') + ->andWhere('t2.deleted')->eq(0) + ->fetchGroup('assignedTo'); + $assign = array(); + foreach($bugs as $user => $userBugs) + { + if($user) + { + foreach($userBugs as $bug) + { + $assign[$user]['bug'][$bug->productName]['count'] = isset($assign[$user]['bug'][$bug->productName]['count']) ? $assign[$user]['bug'][$bug->productName]['count'] + 1 : 1; + $assign[$user]['total']['count'] = isset($assign[$user]['total']['count']) ? $assign[$user]['total']['count'] + 1 : 1; + } + } + } + unset($assign['closed']); + return $assign; + } +} diff --git a/trunk/module/report/view/blockreportlist.html.php b/trunk/module/report/view/blockreportlist.html.php new file mode 100644 index 0000000000..be63a71cba --- /dev/null +++ b/trunk/module/report/view/blockreportlist.html.php @@ -0,0 +1,13 @@ +
      report->list;?>
      +
      +
        +reportList->$submenu->lists); + foreach($lang->reportList->$submenu->lists as $list) + { + list($label, $module, $method) = explode('|', $list); + echo '
      • ' . html::a($this->createLink($module, $method), $label) . '
      • '; + } +?> +
      +
      diff --git a/trunk/module/report/view/bugassign.html.php b/trunk/module/report/view/bugassign.html.php new file mode 100644 index 0000000000..f85949e56b --- /dev/null +++ b/trunk/module/report/view/bugassign.html.php @@ -0,0 +1,42 @@ + + + + + + + +
      + + + + + + + + + + + + + $assign):?> + + + + + $count):?> + ';?> + + + + + + '; $id ++;?> + + + + +
      report->user;?>report->product;?>report->bug;?>report->total;?>
      + +
      +
      + diff --git a/trunk/module/report/view/bugsummary.html.php b/trunk/module/report/view/bugsummary.html.php new file mode 100644 index 0000000000..430797d834 --- /dev/null +++ b/trunk/module/report/view/bugsummary.html.php @@ -0,0 +1,52 @@ + + + + + + + + + +
      + + +
      + {$lang->report->to} " . html::input('date', $end, "class='select-7 date' onchange='changeDate(\"$begin\", this.value)'");?> +
      + + + + + + + + + + + + + + + + + + $bug):?> + + + + + + + + + + + + + + + + +
      bug->openedBy;?>report->total;?>bug->unResolved;?>bug->resolutionList['bydesign'];?>bug->resolutionList['duplicate'];?>bug->resolutionList['external'];?>bug->resolutionList['fixed'];?>bug->resolutionList['notrepro'];?>bug->resolutionList['postponed'];?>bug->resolutionList['willnotfix'];?>bug->resolutionList['tostory'];?>
      +
      + diff --git a/trunk/module/report/view/productinfo.html.php b/trunk/module/report/view/productinfo.html.php new file mode 100644 index 0000000000..56c28aed81 --- /dev/null +++ b/trunk/module/report/view/productinfo.html.php @@ -0,0 +1,68 @@ + + + + + + + + +
      + +
      report->proVersion;?>
      +
      + + + + + + + + + + + + + + + + + + + plans) ? count($product->plans) : 1;?> + + + plans)):?> + + plans as $plan):?> + + "?> + + + + + + + + "?> + + + + + + + + + + + + + + + + + +
      product->name;?>product->PO;?>productplan->common;?>productplan->begin;?>productplan->end;?>story->statusList['draft'];?>story->statusList['active'];?>story->statusList['changed'];?>story->statusList['closed'];?>
      ' . $product->name . "

      ";?>
      " . $users[$product->PO] . '

      ';?>
      title;?>begin;?>end;?>status['draft']) ? $plan->status['draft'] : 0);?>status['active']) ? $plan->status['active'] : 0);?>status['changed']) ? $plan->status['changed'] : 0);?>status['closed']) ? $plan->status['closed'] : 0);?>
      +
      + diff --git a/trunk/module/report/view/projectdeviation.html.php b/trunk/module/report/view/projectdeviation.html.php new file mode 100644 index 0000000000..ef82da27ae --- /dev/null +++ b/trunk/module/report/view/projectdeviation.html.php @@ -0,0 +1,110 @@ + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + $project):?> + + + + + + + devConsumed = isset($project->devConsumed) ? $project->devConsumed : 0; + $project->testConsumed = isset($project->testConsumed) ? $project->testConsumed : 0; + $project->estimate = isset($project->estimate) ? $project->estimate : 0; + $project->consumed = isset($project->consumed) ? $project->consumed : 0; + ?> + + + + + + consumed - $project->estimate;?> + + + + + +
      report->id;?>report->project;?>report->task;?>report->stories;?>report->bugs;?>report->devConsumed;?>report->testConsumed;?>report->devTestRate;?>report->estimate;?>report->consumed;?>report->deviation;?>report->deviationRate;?>
      name;?>tasks) ? $project->tasks : 0;?>stories) ? $project->stories : 0;?>bugs) ? $project->bugs : 0;?>devConsumed;?>testConsumed;?>devConsumed / (($project->testConsumed < 1) ? 1 : $project->testConsumed), 1);?>estimate;?>consumed;?> + 0) + { + echo '' . $deviation; + } + else if($deviation < 0) + { + echo '' . abs($deviation); + } + else + { + echo '0'; + } + ?> + + estimate) + { + $num = round($deviation / $project->estimate * 100, 2); + if($num >= 50) + { + echo '' . $num . '%'; + } + else if($num >= 30) + { + echo '' . $num . '%'; + } + else if($num >= 10) + { + echo '' . $num . '%'; + } + else if($num > 0) + { + echo '' . abs($num) . '%'; + } + else if($num <= -20) + { + echo '' . abs($num) . '%'; + } + else if($num < 0) + { + echo '' . abs($num) . '%'; + } + else + { + echo '' . abs($num) . '%'; + } + } + else + { + echo '0%'; + } + ?> +
      +
      + diff --git a/trunk/module/report/view/workload.html.php b/trunk/module/report/view/workload.html.php new file mode 100644 index 0000000000..fd5f97dbaa --- /dev/null +++ b/trunk/module/report/view/workload.html.php @@ -0,0 +1,55 @@ + + + + + + + + +
      + + + + + + + + + + + + + + + + $load):?> + + + + + $info):?> + + ';?> + + + + + + + + '; $id ++;?> + + + + + + +
      report->user;?>report->project;?>report->task;?>report->remain;?>report->taskTotal;?>report->manhourTotal;?>
      + + + +
      +
      + diff --git a/trunk/module/search/config.php b/trunk/module/search/config.php new file mode 100644 index 0000000000..2194553088 --- /dev/null +++ b/trunk/module/search/config.php @@ -0,0 +1,2 @@ +search->groupItems = 3; diff --git a/trunk/module/search/control.php b/trunk/module/search/control.php new file mode 100644 index 0000000000..285e18c786 --- /dev/null +++ b/trunk/module/search/control.php @@ -0,0 +1,76 @@ + + * @package search + * @version $Id$ + * @link http://www.zentao.net + */ +class search extends control +{ + /** + * Build search form. + * + * @param string $module + * @param array $searchFields + * @param array $fieldParams + * @param string $actionURL + * @param int $queryID + * @access public + * @return void + */ + public function buildForm($module, $searchFields, $fieldParams, $actionURL, $queryID = 0) + { + $this->search->initSession($module, $searchFields, $fieldParams); + + $this->view->module = $module; + $this->view->groupItems = $this->config->search->groupItems; + $this->view->searchFields = $searchFields; + $this->view->actionURL = $actionURL; + $this->view->fieldParams = $this->search->setDefaultParams($searchFields, $fieldParams); + $this->view->queries = $this->search->getQueryPairs($module); + $this->view->queryID = $queryID; + $this->display(); + } + + /** + * Build query + * + * @access public + * @return void + */ + public function buildQuery() + { + $this->search->buildQuery(); + die(js::locate($this->post->actionURL, 'parent')); + } + + /** + * Save search query. + * + * @access public + * @return void + */ + public function saveQuery() + { + $this->search->saveQuery(); + if(dao::isError()) die(js::error(dao::getError())); + die('success'); + } + + /** + * Delete a query + * + * @param int $queryID + * @access public + * @return void + */ + public function deleteQuery($queryID) + { + $this->dao->delete()->from(TABLE_USERQUERY)->where('id')->eq($queryID)->andWhere('account')->eq($this->app->user->account)->exec(); + die(js::reload('parent')); + } +} diff --git a/trunk/module/search/lang/en.php b/trunk/module/search/lang/en.php new file mode 100644 index 0000000000..d5e07ed118 --- /dev/null +++ b/trunk/module/search/lang/en.php @@ -0,0 +1,47 @@ + + * @package search + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->search->common = 'Search'; +$lang->search->reset = 'Reset'; +$lang->search->more = 'More'; +$lang->search->lite = 'Lite'; +$lang->search->saveQuery = 'Save'; +$lang->search->myQuery = 'My queries'; +$lang->search->group1 = '1'; +$lang->search->group2 = '2'; +$lang->search->buildForm = 'Search form'; +$lang->search->buildQuery = 'Execute query'; +$lang->search->saveQuery = 'Save query'; +$lang->search->deleteQuery = 'Delete query'; +$lang->search->setQueryTitle = 'Please input the title(execute searching before save):'; +$lang->search->storyTitle = 'Story title'; +$lang->search->taskTitle = 'Task title'; +$lang->search->select = 'Stories/Tasks filter'; + +$lang->search->operators['='] = '='; +$lang->search->operators['!='] = '!='; +$lang->search->operators['>'] = '>'; +$lang->search->operators['>='] = '>='; +$lang->search->operators['<'] = '<'; +$lang->search->operators['<='] = '<='; +$lang->search->operators['include'] = 'include'; +$lang->search->operators['notinclude'] = 'not include'; +$lang->search->operators['belong'] = 'belongs to'; + +$lang->search->andor['and'] = 'And'; +$lang->search->andor['or'] = 'Or'; + +$lang->search->null = 'Null'; + +$lang->userquery->title = 'Title'; +$lang->userquery->myQueries = 'My queries'; +$lang->userquery->execut = 'Execute'; +$lang->userquery->delete = 'Delete'; diff --git a/trunk/module/search/lang/zh-cn.php b/trunk/module/search/lang/zh-cn.php new file mode 100644 index 0000000000..29425f5965 --- /dev/null +++ b/trunk/module/search/lang/zh-cn.php @@ -0,0 +1,47 @@ + + * @package search + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->search->common = '搜索'; +$lang->search->reset = '重置'; +$lang->search->more = '更多'; +$lang->search->lite = '简洁'; +$lang->search->saveQuery = '保存'; +$lang->search->myQuery = '我的查询'; +$lang->search->group1 = '第一组'; +$lang->search->group2 = '第二组'; +$lang->search->buildForm = '搜索表单'; +$lang->search->buildQuery = '执行搜索'; +$lang->search->saveQuery = '保存查询'; +$lang->search->deleteQuery = '删除查询'; +$lang->search->setQueryTitle = '请输入查询标题(保存之前请先查询):'; +$lang->search->storyTitle = '需求名称'; +$lang->search->taskTitle = '任务名称'; +$lang->search->select = '需求/任务筛选'; + +$lang->search->operators['='] = '='; +$lang->search->operators['!='] = '!='; +$lang->search->operators['>'] = '>'; +$lang->search->operators['>='] = '>='; +$lang->search->operators['<'] = '<'; +$lang->search->operators['<='] = '<='; +$lang->search->operators['include'] = '包含'; +$lang->search->operators['notinclude'] = '不包含'; +$lang->search->operators['belong'] = '从属于'; + +$lang->search->andor['and'] = '并且'; +$lang->search->andor['or'] = '或者'; + +$lang->search->null = '空'; + +$lang->userquery->title = '查询标题'; +$lang->userquery->myQueries = '我的查询'; +$lang->userquery->execut = '执行'; +$lang->userquery->delete = '删除'; diff --git a/trunk/module/search/lang/zh-tw.php b/trunk/module/search/lang/zh-tw.php new file mode 100644 index 0000000000..59f4584845 --- /dev/null +++ b/trunk/module/search/lang/zh-tw.php @@ -0,0 +1,47 @@ + + * @package search + * @version $Id: zh-tw.php 3083 2012-06-22 09:45:14Z wwccss $ + * @link http://www.zentao.net + */ +$lang->search->common = '搜索'; +$lang->search->reset = '重置'; +$lang->search->more = '更多'; +$lang->search->lite = '簡潔'; +$lang->search->saveQuery = '保存'; +$lang->search->myQuery = '我的查詢'; +$lang->search->group1 = '第一組'; +$lang->search->group2 = '第二組'; +$lang->search->buildForm = '搜索表單'; +$lang->search->buildQuery = '執行搜索'; +$lang->search->saveQuery = '保存查詢'; +$lang->search->deleteQuery = '刪除查詢'; +$lang->search->setQueryTitle = '請輸入查詢標題(保存之前請先查詢):'; +$lang->search->storyTitle = '需求名稱'; +$lang->search->taskTitle = '任務名稱'; +$lang->search->select = '需求/任務篩選'; + +$lang->search->operators['='] = '='; +$lang->search->operators['!='] = '!='; +$lang->search->operators['>'] = '>'; +$lang->search->operators['>='] = '>='; +$lang->search->operators['<'] = '<'; +$lang->search->operators['<='] = '<='; +$lang->search->operators['include'] = '包含'; +$lang->search->operators['notinclude'] = '不包含'; +$lang->search->operators['belong'] = '從屬於'; + +$lang->search->andor['and'] = '並且'; +$lang->search->andor['or'] = '或者'; + +$lang->search->null = '空'; + +$lang->userquery->title = '查詢標題'; +$lang->userquery->myQueries = '我的查詢'; +$lang->userquery->execut = '執行'; +$lang->userquery->delete = '刪除'; diff --git a/trunk/module/search/model.php b/trunk/module/search/model.php new file mode 100644 index 0000000000..1189213b7e --- /dev/null +++ b/trunk/module/search/model.php @@ -0,0 +1,281 @@ + + * @package search + * @version $Id$ + * @link http://www.zentao.net + */ +?> +config->search->groupItems; + $groupAndOr = strtoupper($this->post->groupAndOr); + if($groupAndOr != 'AND' and $groupAndOr != 'OR') $groupAndOr = 'AND'; + + for($i = 1; $i <= $groupItems * 2; $i ++) + { + /* The and or between two groups. */ + if($i == 1) $where .= '( 1 '; + if($i == $groupItems + 1) $where .= " ) $groupAndOr ( 1 "; + + /* Set var names. */ + $fieldName = "field$i"; + $andOrName = "andOr$i"; + $operatorName = "operator$i"; + $valueName = "value$i"; + + /* Skip empty values. */ + if($this->post->$valueName == false) continue; + if($this->post->$valueName == 'null') $this->post->$valueName = ''; // Null is special, stands to empty. + + /* Set and or. */ + $andOr = strtoupper($this->post->$andOrName); + if($andOr != 'AND' and $andOr != 'OR') $andOr = 'AND'; + $where .= " $andOr "; + + /* Set filed name. */ + $where .= '`' . $this->post->$fieldName . '` '; + + /* Set operator. */ + $value = $this->post->$valueName; + $operator = $this->post->$operatorName; + if(!isset($this->lang->search->operators[$operator])) $operator = '='; + if($operator == "include") + { + $where .= ' LIKE ' . $this->dbh->quote("%$value%"); + } + elseif($operator == "notinclude") + { + $where .= ' NOT LIKE ' . $this->dbh->quote("%$value%"); + } + elseif($operator == 'belong') + { + if($this->post->$fieldName == 'module') + { + $allModules = $this->loadModel('tree')->getAllChildId($value); + $where .= helper::dbIN($allModules); + } + else + { + $where .= ' = ' . $this->dbh->quote($value) . ' '; + } + } + else + { + $where .= $operator . ' ' . $this->dbh->quote($value) . ' '; + } + } + + $where .=" )"; + + /* Save to session. */ + $querySessionName = $this->post->module . 'Query'; + $formSessionName = $this->post->module . 'Form'; + $this->session->set($querySessionName, $where); + $this->session->set($formSessionName, $_POST); + } + + /** + * Init the search session for the first time search. + * + * @param string $module + * @param array $fields + * @param array $fieldParams + * @access public + * @return void + */ + public function initSession($module, $fields, $fieldParams) + { + $formSessionName = $module . 'Form'; + if($this->session->$formSessionName != false) return; + + for($i = 1; $i <= $this->config->search->groupItems * 2; $i ++) + { + /* Var names. */ + $fieldName = "field$i"; + $andOrName = "andOr$i"; + $operatorName = "operator$i"; + $valueName = "value$i"; + + $currentField = key($fields); + $operator = isset($fieldParams[$currentField]['operator']) ? $fieldParams[$currentField]['operator'] : '='; + + $queryForm[$fieldName] = key($fields); + $queryForm[$andOrName] = 'and'; + $queryForm[$operatorName] = $operator; + $queryForm[$valueName] = ''; + + if(!next($fields)) reset($fields); + } + $queryForm['groupAndOr'] = 'and'; + $this->session->set($formSessionName, $queryForm); + } + + /** + * Set default params for selection. + * + * @param array $fields + * @param array $params + * @access public + * @return array + */ + public function setDefaultParams($fields, $params) + { + $users = $this->loadModel('user')->getPairs(); + $products = array('' => '') + $this->loadModel('product')->getPairs(); + $projects = array('' => '') + $this->loadModel('project')->getPairs(); + $fields = array_keys($fields); + foreach($fields as $fieldName) + { + if(!isset($params[$fieldName])) $params[$fieldName] = array('operator' => '=', 'control' => 'input', 'values' => ''); + if($params[$fieldName]['values'] == 'users') $params[$fieldName]['values'] = $users; + if($params[$fieldName]['values'] == 'products') $params[$fieldName]['values'] = $products; + if($params[$fieldName]['values'] == 'projects') $params[$fieldName]['values'] = $projects; + if(is_array($params[$fieldName]['values'])) $params[$fieldName]['values'] = $params[$fieldName]['values'] + array('null' => $this->lang->search->null); + } + return $params; + } + + /** + * Get a query. + * + * @param int $queryID + * @access public + * @return string + */ + public function getQuery($queryID) + { + $query = $this->dao->findByID($queryID)->from(TABLE_USERQUERY)->fetch(); + if(!$query) return false; + $query->form = unserialize($query->form); + return $query; + } + + /** + * Save current query to db. + * + * @access public + * @return void + */ + public function saveQuery() + { + $sqlVar = $this->post->module . 'Query'; + $formVar = $this->post->module . 'Form'; + $sql = $this->session->$sqlVar; + if(!$sql) $sql = ' 1 = 1 '; + + $query = fixer::input('post') + ->specialChars('title') + ->add('account', $this->app->user->account) + ->add('form', serialize($this->session->$formVar)) + ->add('sql', $sql) + ->get(); + $this->dao->insert(TABLE_USERQUERY)->data($query)->autoCheck()->check('title', 'notempty')->exec(); + } + + /** + * Get title => id pairs of a user. + * + * @param string $module + * @access public + * @return array + */ + public function getQueryPairs($module) + { + $queries = $this->dao->select('id, title') + ->from(TABLE_USERQUERY) + ->where('account')->eq($this->app->user->account) + ->andWhere('module')->eq($module) + ->orderBy('id_asc') + ->fetchPairs(); + if(!$queries) return array('' => $this->lang->search->myQuery); + $queries = array('' => $this->lang->search->myQuery) + $queries; + return $queries; + } + + /** + * Get records by the conditon. + * + * @param string $module + * @param string $moduleIds + * @param string $conditions + * @access public + * @return array + */ + public function getBySelect($module, $moduleIds, $conditions) + { + if($module == 'story') + { + $pairs = 'id,title'; + $table = 'zt_story'; + } + else if($module == 'task') + { + $pairs = 'id,name'; + $table = 'zt_task'; + } + $query = '`' . $conditions['field1'] . '`'; + $operator = $conditions['operator1']; + $value = $conditions['value1']; + + if(!isset($this->lang->search->operators[$operator])) $operator = '='; + if($operator == "include") + { + $query .= ' LIKE ' . $this->dbh->quote("%$value%"); + } + elseif($operator == "notinclude") + { + $where .= ' NOT LIKE ' . $this->dbh->quote("%$value%"); + } + else + { + $query .= $operator . ' ' . $this->dbh->quote($value) . ' '; + } + + foreach($moduleIds as $id) + { + if(!$id) continue; + $title = $this->dao->select($pairs) + ->from($table) + ->where('id')->eq((int)$id) + ->andWhere($query) + ->fetch(); + if($title) $results[$id] = $title; + } + if(!isset($results)) return array(); + return $this->formatResults($results, $module); + } + + /** + * Format the results. + * + * @param array $results + * @param string $module + * @access public + * @return array + */ + public function formatResults($results, $module) + { + /* Get title field. */ + $title = ($module == 'story') ? 'title' : 'name'; + $resultPairs = array('' => ''); + foreach($results as $result) $resultPairs[$result->id] = $result->id . ':' . $result->$title; + return $resultPairs; + } + +} diff --git a/trunk/module/search/view/buildform.html.php b/trunk/module/search/view/buildform.html.php new file mode 100644 index 0000000000..f14839a406 --- /dev/null +++ b/trunk/module/search/view/buildform.html.php @@ -0,0 +1,282 @@ + + * @package search + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
      ' target='hiddenwin' id='searchform'> + + + + + + + + + + +
        + + session->$formSessionName; + + $fieldNO = 1; + for($i = 1; $i <= $groupItems; $i ++) + { + $spanClass = $i == 1 ? 'inline' : 'hidden'; + echo ""; + + /* Get params of current field. */ + $currentField = $formSession["field$fieldNO"]; + $param = $fieldParams[$currentField]; + + /* Print and or. */ + if($i == 1) echo "{$lang->search->group1}" . html::hidden("andOr$fieldNO", 'AND'); + if($i > 1) echo "
      " . html::select("andOr$fieldNO", $lang->search->andor, $formSession["andOr$fieldNO"]); + + /* Print field. */ + echo html::select("field$fieldNO", $searchFields, $formSession["field$fieldNO"], "onchange='setField(this.value, $fieldNO)'"); + + /* Print operator. */ + echo html::select("operator$fieldNO", $lang->search->operators, $formSession["operator$fieldNO"]); + + /* Print value. */ + echo ""; + if($param['control'] == 'select') echo html::select("value$fieldNO", $param['values'], $formSession["value$fieldNO"], "class='select-2 searchSelect'"); + if($param['control'] == 'input') + { + $fieldName = $formSession["field$fieldNO"]; + $extraClass = isset($param['class']) ? $param['class'] : ''; + echo html::input("value$fieldNO", $formSession["value$fieldNO"], "class='text-2 $extraClass searchInput'"); + } + echo ''; + + $fieldNO ++; + echo '
      '; + } + ?> +
      +
      search->andor, $formSession['groupAndOr'])?> + + "; + + /* Get params of current field. */ + $currentField = $formSession["field$fieldNO"]; + $param = $fieldParams[$currentField]; + + /* Print and or. */ + if($i == 1) echo "{$lang->search->group2}" . html::hidden("andOr$fieldNO", 'AND'); + if($i > 1) echo "
      " . html::select("andOr$fieldNO", $lang->search->andor, $formSession["andOr$fieldNO"]); + + /* Print field. */ + echo html::select("field$fieldNO", $searchFields, $formSession["field$fieldNO"], "onchange='setField(this.value, $fieldNO)'"); + + /* Print operator. */ + echo html::select("operator$fieldNO", $lang->search->operators, $formSession["operator$fieldNO"]); + + /* Print value. */ + echo ""; + if($param['control'] == 'select') echo html::select("value$fieldNO", $param['values'], $formSession["value$fieldNO"], "class='select-2 searchSelect'"); + + if($param['control'] == 'input') + { + $fieldName = $formSession["field$fieldNO"]; + $extraClass = isset($param['class']) ? $param['class'] : ''; + echo html::input("value$fieldNO", $formSession["value$fieldNO"], "class='text-2 $extraClass searchInput'"); + } + echo ''; + + $fieldNO ++; + echo ''; + } + ?> +
      +
      + + search->common); + echo html::commonButton($lang->search->reset, 'onclick=resetForm();'); + echo html::commonButton($lang->save, 'onclick=saveQuery()'); + ?> + + + + +   + + +
      +
      + diff --git a/trunk/module/setting/model.php b/trunk/module/setting/model.php new file mode 100644 index 0000000000..faa257964e --- /dev/null +++ b/trunk/module/setting/model.php @@ -0,0 +1,200 @@ + + * @package setting + * @version $Id$ + * @link http://www.zentao.net + */ +?> +app->user->account); + $records = $this->dao->select('*') + ->from(TABLE_CONFIG) + ->where('owner')->in($owner) + ->fetchAll(); + if(!$records) return array(); + + /* Group records by owner and module. */ + $config = array(); + foreach($records as $record) + { + if(!isset($record->module)) return array(); // If no module field, return directly. Since 3.2 version, there's the module field. + + if($record->section) $config[$record->owner]->{$record->module}[] = $record; + if(!$record->section) $config[$record->owner]->{$record->module}[] = $record; + } + return $config; + } + + /** + * Get the version of current zentaopms. + * + * Since the version field not saved in db. So if empty, return 0.3 beta. + * @access public + * @return void + */ + public function getVersion() + { + $version = $this->dao->select('value')->from(TABLE_CONFIG) + ->where('owner')->eq('system') + ->andWhere('section')->eq('global') + ->andWhere('`key`')->eq('version') + ->andWhere('company')->eq(0) + ->fetch('value', false); + + if($version == '3.0.stable') $version = '3.0'; // convert 3.0.stable to 3.0. + if($version) return $version; + return '0.3 beta'; + } + + /** + * Update version + * + * @param string $version + * @access public + * @return void + */ + public function updateVersion($version) + { + if($version >= 3.2) return $this->setItem('system', 'common', 'global', 'version', $version, 0); + + $this->dao->delete()->from(TABLE_CONFIG) + ->where('owner')->eq('system') + ->andWhere('section')->eq('global') + ->andWhere('`key`')->eq('version') + ->andWhere('company')->eq(0) + ->exec(); + $data->owner = 'system'; + $data->section = 'global'; + $data->key = 'version'; + $data->company = 0; + return $this->dao->insert(TABLE_CONFIG)->data($data, false)->exec(); + } + + /** + * Compute a SN. Use the server ip, and server software string as seed, and an rand number, two micro time + * + * Note: this sn just to unique this zentaopms. No any private info. + * + * @access public + * @return string + */ + public function computeSN() + { + $seed = $this->server->SERVER_ADDR . $this->server->SERVER_SOFTWARE; + $sn = md5(str_shuffle(md5($seed . mt_rand(0, 99999999) . microtime())) . microtime()); + return $sn; + } + + /** + * Set the sn of current zentaopms. + * + * @access public + * @return void + */ + public function setSN() + { + $sn = $this->getItem('system', 'common', 'global', 'sn', 0); + if($sn == '' or + $sn == '281602d8ff5ee7533eeafd26eda4e776' or + $sn == '9bed3108092c94a0db2b934a46268b4a' or + $sn == '8522dd4d76762a49d02261ddbe4ad432' or + $sn == '13593e340ee2bdffed640d0c4eed8bec') + { + $sn = $this->computeSN(); + $this->setItem('system', 'common', 'global', 'sn', $sn, 0); + } + } + + /** + * Get value of an item. + * + * @param string $owner + * @param string $module + * @param string $section + * @param string $key + * @param string|int $company + * @access public + * @return misc + */ + public function getItem($owner, $module, $section, $key, $company = 'current') + { + if($company === 'current') $company = $this->app->company->id; + return $this->dao->select('`value`')->from(TABLE_CONFIG) + ->where('company')->eq($company) + ->andWhere('owner')->eq($owner) + ->andWhere('module')->eq($module) + ->andWhere('section')->eq($section) + ->andWhere('`key`')->eq($key) + ->fetch('value', $autoCompany = false); + } + + /** + * Set value of an item. + * + * @param string $owner + * @param string $module + * @param string $section + * @param string $key + * @param string $value + * @param string|int $company + * @access public + * @return void + */ + public function setItem($owner, $module, $section, $key, $value = '', $company = 'current') + { + $item->company = $company === 'current' ? $this->app->company->id : $company; + $item->owner = $owner; + $item->module = $module; + $item->section = $section; + $item->key = $key; + $item->value = $value; + + $this->dao->replace(TABLE_CONFIG)->data($item)->exec($autoCompany = false); + } + + /** + * Delete value of item + * + * @param string $owner + * @param string $module + * @param string $section + * @param array $key + * @param string|int $company + * @access public + * @return void + */ + public function deleteItem($owner, $module, $section, $key, $company = 'current') + { + $fieldNames = array_keys($key); + $company = $company === 'current' ? $this->app->company->id : $company; + foreach($fieldNames as $fieldName) + { + $value = $key[$fieldName]; + $more = (is_array($value) or is_object($value)) ? true : false; + $this->dao->delete()->from(TABLE_CONFIG) + ->where('owner')->eq($owner) + ->andWhere('module')->eq($module) + ->andWhere('section')->eq($section) + ->andWhere('company')->eq($company) + ->beginIF($more)->andWhere("`$fieldName`")->in($value)->fi() + ->beginIF(!$more)->andWhere("`$fieldName`")->eq($value)->fi() + ->exec($autoCompany = false); + } + } +} diff --git a/trunk/module/story/config.php b/trunk/module/story/config.php new file mode 100644 index 0000000000..8ea478d95a --- /dev/null +++ b/trunk/module/story/config.php @@ -0,0 +1,20 @@ +story->batchCreate = 10; +$config->story->affectedFixedNum = 7; +$config->story->create->requiredFields = 'title'; +$config->story->edit->requiredFields = 'title'; +$config->story->change->requiredFields = 'title'; +$config->story->close->requiredFields = 'closedReason'; +$config->story->review->requiredFields = 'assignedTo,reviewedBy,result'; + +$config->story->editor->create = array('id' => 'spec,verify', 'tools' => 'simpleTools'); +$config->story->editor->change = array('id' => 'spec,verify', 'tools' => 'simpleTools'); + +$config->story->list->exportFields = ' + id, product, module, plan, source, title, spec, verify, keywords, + pri, estimate, status, stage, + openedBy, openedDate, assignedTo, assignedDate, mailto, + reviewedBy, reviewedDate, + closedBy, closedDate, closedReason, + lastEditedBy, lastEditedDate, + childStories, linkStories, duplicateStory, files'; diff --git a/trunk/module/story/control.php b/trunk/module/story/control.php new file mode 100644 index 0000000000..663fc7f51e --- /dev/null +++ b/trunk/module/story/control.php @@ -0,0 +1,914 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +class story extends control +{ + /** + * The construct function, load product, tree, user auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('product'); + $this->loadModel('project'); + $this->loadModel('tree'); + $this->loadModel('user'); + $this->loadModel('action'); + } + + /** + * Create a story. + * + * @param int $productID + * @param int $moduleID + * @access public + * @return void + */ + public function create($productID = 0, $moduleID = 0, $storyID = 0, $projectID = 0, $bugID = 0) + { + if(!empty($_POST)) + { + $storyID = $this->story->create($projectID, $bugID); + if(dao::isError()) die(js::error(dao::getError())); + if($bugID == 0) + { + $actionID = $this->action->create('story', $storyID, 'Opened', ''); + } + else + { + $actionID = $this->action->create('story', $storyID, 'Frombug', '', $bugID); + } + $this->sendMail($storyID, $actionID); + if($this->post->newStory) + { + echo js::alert($this->lang->story->successSaved . $this->lang->story->newStory); + die(js::locate($this->createLink('story', 'create', "productID=$productID&moduleID=$moduleID&story=0&projectID=$projectID&bugID=$bugID"), 'parent')); + } + if($projectID == 0) + { + die(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); + } + else + { + die(js::locate($this->createLink('project', 'story', "projectID=$projectID"), 'parent')); + } + } + + /* Set products, users and module. */ + if($productID != 0) + { + $product = $this->product->getById($productID); + $products = $this->product->getPairs(); + } + else + { + $products = $this->product->getProductsByProject($projectID); + foreach($products as $key => $title) + { + $product = $this->product->getById($key); + break; + } + } + $users = $this->user->getPairs('nodeleted'); + $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story'); + + /* Set menu. */ + $this->product->setMenu($products, $product->id); + + /* Init vars. */ + $planID = 0; + $source = ''; + $pri = 0; + $estimate = ''; + $title = ''; + $spec = ''; + $verify = ''; + $keywords = ''; + $mailto = ''; + + if($storyID > 0) + { + $story = $this->story->getByID($storyID); + $planID = $story->plan; + $source = $story->source; + $pri = $story->pri; + $productID = $story->product; + $moduleID = $story->module; + $estimate = $story->estimate; + $title = $story->title; + $spec = htmlspecialchars($story->spec); + $verify = htmlspecialchars($story->verify); + $keywords = $story->keywords; + $mailto = $story->mailto; + } + + if($bugID > 0) + { + $oldBug = $this->loadModel('bug')->getById($bugID); + $productID = $oldBug->product; + $source = 'bug'; + $title = $oldBug->title; + $keywords = $oldBug->keywords; + $spec = $oldBug->steps; + $pri = $oldBug->pri; + if(strpos($oldBug->mailto, $oldBug->openedBy) === false) + { + $mailto = $oldBug->mailto . $oldBug->openedBy . ','; + } + else + { + $mailto = $oldBug->mailto; + } + } + + $this->view->header->title = $product->name . $this->lang->colon . $this->lang->story->create; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID"), $product->name); + $this->view->position[] = $this->lang->story->create; + $this->view->products = $products; + $this->view->users = $users; + $this->view->moduleID = $moduleID; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->plans = $this->loadModel('productplan')->getPairs($productID, 'unexpired'); + $this->view->planID = $planID; + $this->view->source = $source; + $this->view->pri = $pri; + $this->view->productID = $productID; + $this->view->estimate = $estimate; + $this->view->title = $title; + $this->view->spec = $spec; + $this->view->verify = $verify; + $this->view->keywords = $keywords; + $this->view->mailto = $mailto; + + $this->display(); + } + + /** + * Create a batch stories. + * + * @param int $productID + * @param int $moduleID + * @access public + * @return void + */ + public function batchCreate($productID = 0, $moduleID = 0) + { + if(!empty($_POST)) + { + $mails = $this->story->batchCreate($productID); + if(dao::isError()) die(js::error(dao::getError())); + + foreach($mails as $mail) + { + $this->sendMail($mail->storyID, $mail->actionID); + } + die(js::locate($this->createLink('product', 'browse', "productID=$productID"), 'parent')); + } + + /* Set products, users and module. */ + $product = $this->product->getById($productID); + $products = $this->product->getPairs(); + $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story'); + + /* Set menu. */ + $this->product->setMenu($products, $product->id); + + /* Init vars. */ + $planID = 0; + $pri = 0; + $estimate = ''; + $title = ''; + $spec = ''; + + $moduleOptionMenu['same'] = $this->lang->story->same; + $plans = $this->loadModel('productplan')->getPairs($productID, 'unexpired'); + $plans['same'] = $this->lang->story->same; + + $this->view->header->title = $product->name . $this->lang->colon . $this->lang->story->create; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID"), $product->name); + $this->view->position[] = $this->lang->story->create; + $this->view->products = $products; + $this->view->moduleID = $moduleID; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->plans = $plans; + $this->view->planID = $planID; + $this->view->pri = $pri; + $this->view->productID = $productID; + $this->view->estimate = $estimate; + $this->view->title = $title; + $this->view->spec = $spec; + + $this->display(); + } + + /** + * The common action when edit or change a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function commonAction($storyID) + { + /* Get datas. */ + $story = $this->story->getById($storyID); + $product = $this->product->getById($story->product); + $products = $this->product->getPairs(); + $users = $this->user->getPairs('nodeleted'); + $moduleOptionMenu = $this->tree->getOptionMenu($product->id, $viewType = 'story'); + + /* Set menu. */ + $this->product->setMenu($products, $product->id); + + /* Assign. */ + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id"), $product->name); + $this->view->product = $product; + $this->view->products = $products; + $this->view->story = $story; + $this->view->users = $users; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->plans = $this->loadModel('productplan')->getPairs($product->id); + $this->view->actions = $this->action->getList('story', $storyID); + } + + /** + * Edit a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function edit($storyID) + { + if(!empty($_POST)) + { + $changes = $this->story->update($storyID); + if(dao::isError()) die(js::error(dao::getError())); + if($this->post->comment != '' or !empty($changes)) + { + $action = !empty($changes) ? 'Edited' : 'Commented'; + $actionID = $this->action->create('story', $storyID, $action, $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendMail($storyID, $actionID); + } + die(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); + } + + $this->commonAction($storyID); + + /* Assign. */ + $this->view->header->title = $this->view->product->name . $this->lang->colon . $this->lang->story->edit . $this->lang->colon . $this->view->story->title; + $this->view->position[] = $this->lang->story->edit; + $this->view->users = $this->user->appendDeleted($this->user->getPairs('nodeleted'), $this->view->story->assignedTo); + $this->view->story = $this->story->getById($storyID, 0, true); + $this->display(); + } + + /** + * Change a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function change($storyID) + { + if(!empty($_POST)) + { + $changes = $this->story->change($storyID); + if(dao::isError()) die(js::error(dao::getError())); + $version = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch('version'); + $files = $this->loadModel('file')->saveUpload('story', $storyID, $version); + if($this->post->comment != '' or !empty($changes) or !empty($files)) + { + $action = (!empty($changes) or !empty($files)) ? 'Changed' : 'Commented'; + $fileAction = ''; + if(!empty($files)) $fileAction = $this->lang->addFiles . join(',', $files) . "\n" ; + $actionID = $this->action->create('story', $storyID, $action, $fileAction . $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendMail($storyID, $actionID); + } + die(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); + } + + $this->commonAction($storyID); + $this->story->getAffectedScope($this->view->story); + $this->app->loadLang('task'); + $this->app->loadLang('bug'); + $this->app->loadLang('testcase'); + $this->app->loadLang('project'); + + /* Assign. */ + $this->view->header->title = $this->view->product->name . $this->lang->colon . $this->lang->story->change . $this->lang->colon . $this->view->story->title; + $this->view->position[] = $this->lang->story->change; + $this->display(); + } + + /** + * Activate a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function activate($storyID) + { + if(!empty($_POST)) + { + $this->story->activate($storyID); + if(dao::isError()) die(js::error(dao::getError())); + $actionID = $this->action->create('story', $storyID, 'Activated', $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendMail($storyID, $actionID); + die(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); + } + + $this->commonAction($storyID); + + /* Assign. */ + $this->view->header->title = $this->view->product->name . $this->lang->colon . $this->lang->story->activate . $this->lang->colon . $this->view->story->title; + $this->view->position[] = $this->lang->story->activate; + $this->display(); + } + + /** + * View a story. + * + * @param int $storyID + * @param int $version + * @access public + * @return void + */ + public function view($storyID, $version = 0) + { + $storyID = (int)$storyID; + $story = $this->story->getById($storyID, $version, true); + if(!$story) die(js::error($this->lang->notFound) . js::locate('back')); + + $story->files = $this->loadModel('file')->getByObject('story', $storyID); + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); + $plan = $this->dao->findById($story->plan)->from(TABLE_PRODUCTPLAN)->fetch('title'); + $bugs = $this->dao->select('id,title')->from(TABLE_BUG)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll(); + $fromBug = $this->dao->select('id,title')->from(TABLE_BUG)->where('toStory')->eq($storyID)->fetch(); + $cases = $this->dao->select('id,title')->from(TABLE_CASE)->where('story')->eq($storyID)->fetchAll(); + $modulePath = $this->tree->getParents($story->module); + $users = $this->user->getPairs('noletter'); + + /* Set the menu. */ + $this->product->setMenu($this->product->getPairs(), $product->id); + + $header['title'] = "STORY #$story->id $story->title - $product->name"; + $position[] = html::a($this->createLink('product', 'browse', "product=$product->id"), $product->name); + $position[] = $this->lang->story->view; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->product = $product; + $this->view->plan = $plan; + $this->view->bugs = $bugs; + $this->view->fromBug = $fromBug; + $this->view->cases = $cases; + $this->view->story = $story; + $this->view->users = $users; + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->modulePath = $modulePath; + $this->view->version = $version == 0 ? $story->version : $version; + $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('story', $storyID); + $this->display(); + } + + /** + * Delete a story. + * + * @param int $storyID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($storyID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm($this->lang->story->confirmDelete, $this->createLink('story', 'delete', "story=$storyID&confirm=yes"), ''); + exit; + } + else + { + $this->story->delete(TABLE_STORY, $storyID); + die(js::locate($this->session->storyList, 'parent')); + } + } + + /** + * Review a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function review($storyID) + { + if(!empty($_POST)) + { + $this->story->review($storyID); + if(dao::isError()) die(js::error(dao::getError())); + $result = $this->post->result; + if($this->post->closedReason != '' and strpos('done,postponed,subdivided', $this->post->closedReason) !== false) $result = 'pass'; + $actionID = $this->action->create('story', $storyID, 'Reviewed', $this->post->comment, ucfirst($result)); + $this->action->logHistory($actionID, array()); + $this->sendMail($storyID, $actionID); + if($this->post->result == 'reject') + { + $this->action->create('story', $storyID, 'Closed', '', ucfirst($this->post->closedReason)); + } + die(js::locate(inlink('view', "storyID=$storyID"), 'parent')); + } + + /* Get story and product. */ + $story = $this->story->getById($storyID); + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); + + /* Set menu. */ + $this->product->setMenu($this->product->getPairs(), $product->id); + + /* Set the review result options. */ + if($story->status == 'draft' and $story->version == 1) unset($this->lang->story->reviewResultList['revert']); + if($story->status == 'changed') unset($this->lang->story->reviewResultList['reject']); + + $this->view->header->title = $product->name . $this->lang->colon . $this->lang->story->view . $this->lang->colon . $story->title; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id"), $product->name); + $this->view->position[] = $this->lang->story->view; + + $this->view->product = $product; + $this->view->story = $story; + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->users = $this->loadModel('user')->getPairs('nodeleted'); + + /* Get the affcected things. */ + $this->story->getAffectedScope($this->view->story); + $this->app->loadLang('task'); + $this->app->loadLang('bug'); + $this->app->loadLang('testcase'); + $this->app->loadLang('project'); + + $this->display(); + } + + /** + * Close a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function close($storyID) + { + if(!empty($_POST)) + { + $changes = $this->story->close($storyID); + if(dao::isError()) die(js::error(dao::getError())); + $actionID = $this->action->create('story', $storyID, 'Closed', $this->post->comment, ucfirst($this->post->closedReason)); + $this->action->logHistory($actionID, $changes); + $this->sendMail($storyID, $actionID); + die(js::locate(inlink('view', "storyID=$storyID"), 'parent')); + } + + /* Get story and product. */ + $story = $this->story->getById($storyID); + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); + + /* Set menu. */ + $this->product->setMenu($this->product->getPairs(), $product->id); + + /* Set the closed reason options. */ + if($story->status == 'draft') unset($this->lang->story->reasonList['cancel']); + + $this->view->header->title = $product->name . $this->lang->colon . $this->lang->close . $this->lang->colon . $story->title; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id"), $product->name); + $this->view->position[] = $this->lang->close; + + $this->view->product = $product; + $this->view->story = $story; + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->users = $this->loadModel('user')->getPairs(); + $this->display(); + } + + /** + * Batch close story. + * + * @param string $from productBrowse|projectStory|storyBatchClose + * @param int $productID + * @param int $projectID + * @param string $orderBy + * @access public + * @return void + */ + public function batchClose($from = '', $productID = 0, $projectID = 0, $orderBy = '') + { + /* Get post data for product-Browse or project-Story. */ + if($from == 'productBrowse' or $from == 'projectStory') + { + /* Init vars. */ + $editedStories = array(); + $storyIDList = $this->post->storyIDList ? $this->post->storyIDList : array(); + $columns = 4; + $showSuhosinInfo = false; + + /* Get all stories. */ + if(!$projectID) + { + /* Set menu. */ + $this->product->setMenu($this->product->getPairs('nodeleted'), $productID); + $allStories = $this->dao->select('*')->from(TABLE_STORY)->where($this->session->storyQueryCondition)->orderBy($orderBy)->fetchAll('id'); + } + else + { + $this->lang->story->menu = $this->lang->project->menu; + $this->lang->story->menuOrder = $this->lang->project->menuOrder; + $this->project->setMenu($this->project->getPairs('nodeleted'), $projectID); + $this->lang->set('menugroup.story', 'project'); + $allStories = $this->story->getProjectStories($projectID, $orderBy); + } + if(!$allStories) $allStories = array(); + + /* Initialize the stories whose need to edited. */ + foreach($allStories as $story) if(in_array($story->id, $storyIDList)) $editedStories[$story->id] = $story; + + /* Judge whether the editedStories is too large. */ + $showSuhosinInfo = $this->loadModel('common')->judgeSuhosinSetting(count($editedStories), $columns); + + /* Set the sessions. */ + $this->app->session->set('showSuhosinInfo', $showSuhosinInfo); + + /* Assign. */ + if(!$projectID) + { + $product = $this->product->getByID($productID); + $this->view->header['title'] = $product->name . $this->lang->colon . $this->lang->story->batchClose; + } + else + { + $project = $this->project->getByID($projectID); + $this->view->header['title'] = $project->name . $this->lang->colon . $this->lang->story->batchClose; + } + if($showSuhosinInfo) $this->view->suhosinInfo = $this->lang->suhosinInfo; + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $this->lang->story->batchClose; + $this->view->users = $this->loadModel('user')->getPairs('nodeleted'); + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story'); + $this->view->plans = $this->loadModel('productplan')->getPairs($productID); + $this->view->productID = $productID; + $this->view->editedStories = $editedStories; + + $this->display(); + } + /* Get post data for story-batchClose. */ + elseif($from == 'storyBatchClose') + { + if(!empty($_POST)) + { + + $allChanges = $this->story->batchClose(); + + if($allChanges) + { + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, 'Closed', $this->post->comments[$storyID], ucfirst($this->post->closedReasons[$storyID])); + $this->action->logHistory($actionID); + $this->sendMail($storyID, $actionID); + } + } + } + die(js::locate($this->session->storyList, 'parent')); + } + } + + /** + * Tasks of a story. + * + * @param int $storyID + * @param int $projectID + * @access public + * @return void + */ + public function tasks($storyID, $projectID = 0) + { + $this->loadModel('task'); + $this->view->tasks = $this->task->getStoryTaskPairs($storyID, $projectID); + $this->display(); + exit; + } + + /** + * AJAX: get stories of a project in html select. + * + * @param int $projectID + * @param int $productID + * @param int $storyID + * @access public + * @return void + */ + public function ajaxGetProjectStories($projectID, $productID = 0, $storyID = 0) + { + $stories = $this->story->getProjectStoryPairs($projectID, $productID); + die(html::select('story', $stories, $storyID)); + } + + /** + * AJAX: get stories of a product in html select. + * + * @param int $productID + * @param int $moduleID + * @param int $storyID + * @access public + * @return void + */ + public function ajaxGetProductStories($productID, $moduleID = 0, $storyID = 0) + { + $stories = $this->story->getProductStoryPairs($productID, $moduleID); + die(html::select('story', $stories, $storyID, "class=''")); + } + + /** + * Send email. + * + * @param int $storyID + * @param int $actionID + * @access public + * @return void + */ + public function sendmail($storyID, $actionID) + { + $story = $this->story->getById($storyID); + $productName = $this->product->getById($story->product)->name; + + /* Get actions. */ + $action = $this->action->getById($actionID); + $history = $this->action->getHistory($actionID); + $action->history = isset($history[$actionID]) ? $history[$actionID] : array(); + if(strtolower($action->action) == 'opened') $action->comment = $story->spec; + + /* Set toList and ccList. */ + $toList = $story->assignedTo; + $ccList = str_replace(' ', '', trim($story->mailto, ',')); + + /* If the action is changed or reviewed, mail to the project team. */ + if(strtolower($action->action) == 'changed' or strtolower($action->action) == 'reviewed') + { + $prjMembers = $this->story->getProjectMembers($storyID); + if($prjMembers) + { + $ccList .= ',' . join(',', $prjMembers); + $ccList = ltrim($ccList, ','); + } + } + + if($toList == '') + { + if($ccList == '') return; + if(strpos($ccList, ',') === false) + { + $toList = $ccList; + $ccList = ''; + } + else + { + $commaPos = strpos($ccList, ','); + $toList = substr($ccList, 0, $commaPos); + $ccList = substr($ccList, $commaPos + 1); + } + } + elseif($toList == 'closed') + { + $toList = $story->openedBy; + } + + /* Get the mail content. */ + if($action->action == 'opened') $action->comment = ''; + $this->view->story = $story; + $this->view->action = $action; + $this->view->users = $this->user->getPairs('noletter'); + $mailContent = $this->parse($this->moduleName, 'sendmail'); + + /* Send it. */ + $this->loadModel('mail')->send($toList, $productName . ':' . 'STORY #' . $story->id . $this->lang->colon . $story->title, $mailContent, $ccList); + if($this->mail->isError()) echo js::error($this->mail->getError()); + } + /** + * The report page. + * + * @param int $productID + * @param string $browseType + * @param int $moduleID + * @access public + * @return void + */ + public function report($productID, $browseType, $moduleID) + { + $this->loadModel('report'); + $this->view->charts = array(); + $this->view->renderJS = ''; + + if(!empty($_POST)) + { + foreach($this->post->charts as $chart) + { + $chartFunc = 'getDataOf' . $chart; + $chartData = $this->story->$chartFunc(); + $chartOption = $this->lang->story->report->$chart; + $this->story->mergeChartOption($chart); + + $chartXML = $this->report->createSingleXML($chartData, $chartOption->graph); + $this->view->charts[$chart] = $this->report->createJSChart($chartOption->swf, $chartXML, $chartOption->width, $chartOption->height); + $this->view->datas[$chart] = $this->report->computePercent($chartData); + } + $this->view->renderJS = $this->report->renderJsCharts(count($this->view->charts)); + } + $this->products = $this->product->getPairs(); + $this->product->setMenu($this->products, $productID); + $this->view->header->title = $this->products[$productID] . $this->lang->colon . $this->lang->story->common; + $this->view->productID = $productID; + $this->view->browseType = $browseType; + $this->view->moduleID = $moduleID; + $this->view->checkedCharts = $this->post->charts ? join(',', $this->post->charts) : ''; + $this->display(); + } + + /** + * get data to export + * + * @param int $productID + * @param string $orderBy + * @access public + * @return void + */ + public function export($productID, $orderBy) + { + /* format the fields of every story in order to export data. */ + if($_POST) + { + $storyLang = $this->lang->story; + $storyConfig = $this->config->story; + + /* Create field lists. */ + $fields = explode(',', $storyConfig->list->exportFields); + foreach($fields as $key => $fieldName) + { + $fieldName = trim($fieldName); + $fields[$fieldName] = isset($storyLang->$fieldName) ? $storyLang->$fieldName : $fieldName; + unset($fields[$key]); + } + + /* Get stories. */ + $stories = $this->dao->select('*')->from(TABLE_STORY)->where($this->session->storyQueryCondition)->orderBy($orderBy)->fetchAll('id', false); + + /* Get users, products and projects. */ + $users = $this->loadModel('user')->getPairs('noletter'); + $products = $this->loadModel('product')->getPairs(); + + /* Get related objects id lists. */ + $relatedModuleIdList = array(); + $relatedStoryIdList = array(); + $relatedPlanIdList = array(); + + foreach($stories as $story) + { + $relatedModuleIdList[$story->module] = $story->module; + $relatedPlanIdList[$story->plan] = $story->plan; + + /* Process related stories. */ + $relatedStories = $story->childStories . ',' . $story->linkStories . ',' . $story->duplicateStory; + $relatedStories = explode(',', $relatedStories); + foreach($relatedStories as $storyID) + { + if($storyID) $relatedStoryIdList[$storyID] = trim($storyID); + } + } + + /* Get related objects title or names. */ + $relatedModules = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in($relatedModuleIdList)->fetchPairs(); + $relatedPlans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in($relatedPlanIdList)->fetchPairs(); + $relatedStories = $this->dao->select('id,title')->from(TABLE_STORY) ->where('id')->in($relatedStoryIdList)->fetchPairs(); + $relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('story')->andWhere('objectID')->in(@array_keys($stories))->fetchGroup('objectID'); + $relatedSpecs = $this->dao->select('*')->from(TABLE_STORYSPEC)->where('`story`')->in(@array_keys($stories))->orderBy('version desc')->fetchGroup('story'); + + foreach($stories as $story) + { + $story->spec = ''; + $story->verify = ''; + if(isset($relatedSpecs[$story->id])) + { + $storySpec = $relatedSpecs[$story->id][0]; + $story->title = $storySpec->title; + $story->spec = $storySpec->spec; + $story->verify = $storySpec->verify; + } + + if($this->post->fileType == 'csv') + { + $story->spec = htmlspecialchars_decode($story->spec); + $story->spec = str_replace("
      ", "\n", $story->spec); + $story->spec = str_replace('"', '""', $story->spec); + + $story->verify = htmlspecialchars_decode($story->verify); + $story->verify = str_replace("
      ", "\n", $story->verify); + $story->verify = str_replace('"', '""', $story->verify); + } + /* fill some field with useful value. */ + if(isset($products[$story->product])) $story->product = $products[$story->product]; + if(isset($relatedModules[$story->module])) $story->module = $relatedModules[$story->module]; + if(isset($relatedPlans[$story->plan])) $story->plan = $relatedPlans[$story->plan]; + if(isset($relatedStories[$story->duplicateStory])) $story->duplicateStory = $relatedStories[$story->duplicateStory]; + + if(isset($storyLang->priList[$story->pri])) $story->pri = $storyLang->priList[$story->pri]; + if(isset($storyLang->statusList[$story->status])) $story->status = $storyLang->statusList[$story->status]; + if(isset($storyLang->stageList[$story->stage])) $story->stage = $storyLang->stageList[$story->stage]; + if(isset($storyLang->reasonList[$story->closedReason])) $story->closedReason = $storyLang->reasonList[$story->closedReason]; + + if(isset($users[$story->openedBy])) $story->openedBy = $users[$story->openedBy]; + if(isset($users[$story->assignedTo])) $story->assignedTo = $users[$story->assignedTo]; + if(isset($users[$story->lastEditedBy])) $story->lastEditedBy = $users[$story->lastEditedBy]; + if(isset($users[$story->closedBy])) $story->closedBy = $users[$story->closedBy]; + + $story->openedDate = substr($story->openedDate, 0, 10); + $story->assignedDate = substr($story->assignedDate, 0, 10); + $story->lastEditedDate = substr($story->lastEditedDate, 0, 10); + $story->closedDate = substr($story->closedDate, 0, 10); + + + if($story->linkStories) + { + $tmpLinkStories = array(); + $linkStoriesIdList = explode(',', $story->linkStories); + foreach($linkStoriesIdList as $linkStoryID) + { + $linkStoryID = trim($linkStoryID); + $tmpLinkStories[] = isset($relatedStories[$linkStoryID]) ? $relatedStories[$linkStoryID] : $linkStoryID; + } + $story->linkStories = join("; \n", $tmpLinkStories); + } + + if($story->childStories) + { + $tmpChildStories = array(); + $childStoriesIdList = explode(',', $story->childStories); + foreach($childStoriesIdList as $childStoryID) + { + $childStoryID = trim($childStoryID); + $tmpChildStories[] = isset($relatedStories[$childStoryID]) ? $relatedStories[$childStoryID] : $childStoryID; + } + $story->childStories = join("; \n", $tmpChildStories); + } + + /* Set related files. */ + if(isset($relatedFiles[$story->id])) + { + foreach($relatedFiles[$story->id] as $file) + { + $fileURL = 'http://' . $this->server->http_host . $this->config->webRoot . "data/upload/$story->company/" . $file->pathname; + $story->files .= html::a($fileURL, $file->title, '_blank') . '
      '; + } + } + + $story->mailto = trim(trim($story->mailto), ','); + $mailtos = explode(',', $story->mailto); + $story->mailto = ''; + foreach($mailtos as $mailto) + { + $mailto = trim($mailto); + if(isset($users[$mailto])) $story->mailto .= $users[$mailto] . ','; + } + + $story->reviewedBy = trim(trim($story->reviewedBy), ','); + $reviewedBys = explode(',', $story->reviewedBy); + $story->reviewedBy = ''; + foreach($reviewedBys as $reviewedBy) + { + $reviewedBy = trim($reviewedBy); + if(isset($users[$reviewedBy])) $story->reviewedBy .= $users[$reviewedBy] . ','; + } + + } + + $this->post->set('fields', $fields); + $this->post->set('rows', $stories); + $this->post->set('kind', 'story'); + $this->fetch('file', 'export2' . $this->post->fileType, $_POST); + } + + $this->display(); + } +} diff --git a/trunk/module/story/css/batchcreate.css b/trunk/module/story/css/batchcreate.css new file mode 100644 index 0000000000..5468bcc6c8 --- /dev/null +++ b/trunk/module/story/css/batchcreate.css @@ -0,0 +1,6 @@ +.text-1, .select-1{height:30px} +.w-300px{width:300px} +.w-280px{width:280px} +.w-180px{width:180px} +.half-left {text-align:right} +.half-right{text-align:left} diff --git a/trunk/module/story/css/change.css b/trunk/module/story/css/change.css new file mode 100644 index 0000000000..9e90cd2d63 --- /dev/null +++ b/trunk/module/story/css/change.css @@ -0,0 +1,2 @@ +.headerTable {border-bottom:none; margin-bottom:0px} +.contentTable {border-top:none} diff --git a/trunk/module/story/css/common.css b/trunk/module/story/css/common.css new file mode 100644 index 0000000000..9e5799cb16 --- /dev/null +++ b/trunk/module/story/css/common.css @@ -0,0 +1 @@ +.linkbox{height:180px; overflow-y:auto} diff --git a/trunk/module/story/css/create.css b/trunk/module/story/css/create.css new file mode 100644 index 0000000000..dce00e9044 --- /dev/null +++ b/trunk/module/story/css/create.css @@ -0,0 +1 @@ +#plan {width:245px} diff --git a/trunk/module/story/css/edit.css b/trunk/module/story/css/edit.css new file mode 100644 index 0000000000..35f5f4cea0 --- /dev/null +++ b/trunk/module/story/css/edit.css @@ -0,0 +1 @@ +#module, #plan {width:90%} diff --git a/trunk/module/story/css/report.css b/trunk/module/story/css/report.css new file mode 100644 index 0000000000..9c3849153e --- /dev/null +++ b/trunk/module/story/css/report.css @@ -0,0 +1 @@ +.side span {display:block} diff --git a/trunk/module/story/css/tasks.css b/trunk/module/story/css/tasks.css new file mode 100644 index 0000000000..620aa31b05 --- /dev/null +++ b/trunk/module/story/css/tasks.css @@ -0,0 +1 @@ +body{background:white} diff --git a/trunk/module/story/js/batchclose.js b/trunk/module/story/js/batchclose.js new file mode 100755 index 0000000000..45150d3088 --- /dev/null +++ b/trunk/module/story/js/batchclose.js @@ -0,0 +1,26 @@ +/** + * Set duplicate field. + * + * @param string $resolution + * @param int $storyID + * @access public + * @return void + */ +function setDuplicateAndChild(resolution, storyID) +{ + if(resolution == 'duplicate') + { + $('#childStoryBox' + storyID).hide(); + $('#duplicateStoryBox' + storyID).show(); + } + else if(resolution == 'subdivided') + { + $('#duplicateStoryBox' + storyID).hide(); + $('#childStoryBox' + storyID).show(); + } + else + { + $('#duplicateStoryBox' + storyID).hide(); + $('#childStoryBox' + storyID).hide(); + } +} diff --git a/trunk/module/story/js/close.js b/trunk/module/story/js/close.js new file mode 100644 index 0000000000..73b0e1988a --- /dev/null +++ b/trunk/module/story/js/close.js @@ -0,0 +1,18 @@ +function setStory(reason) +{ + if(reason == 'duplicate') + { + $('#duplicateStoryBox').show(); + $('#childStoriesBox').hide(); + } + else if(reason == 'subdivided') + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').show(); + } + else + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + } +} diff --git a/trunk/module/story/js/review.js b/trunk/module/story/js/review.js new file mode 100644 index 0000000000..04776e8422 --- /dev/null +++ b/trunk/module/story/js/review.js @@ -0,0 +1,45 @@ +function switchShow(result) +{ + if(result == 'reject') + { + $('#rejectedReasonBox').show(); + $('#preVersionBox').hide(); + $('#assignedTo').val('closed'); + } + else if(result == 'revert') + { + $('#preVersionBox').show(); + $('#rejectedReasonBox').hide(); + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + $('#assignedTo').val(assignedTo); + } + else + { + $('#preVersionBox').hide(); + $('#rejectedReasonBox').hide(); + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + $('#rejectedReasonBox').hide(); + $('#assignedTo').val(assignedTo); + } +} + +function setStory(reason) +{ + if(reason == 'duplicate') + { + $('#duplicateStoryBox').show(); + $('#childStoriesBox').hide(); + } + else if(reason == 'subdivided') + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').show(); + } + else + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + } +} diff --git a/trunk/module/story/js/view.js b/trunk/module/story/js/view.js new file mode 100644 index 0000000000..c6e0d959f4 --- /dev/null +++ b/trunk/module/story/js/view.js @@ -0,0 +1,4 @@ +function setComment() +{ + $('#comment').toggle(); +} diff --git a/trunk/module/story/lang/en.php b/trunk/module/story/lang/en.php new file mode 100644 index 0000000000..f289aa9d52 --- /dev/null +++ b/trunk/module/story/lang/en.php @@ -0,0 +1,236 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->story->browse = "Browse"; +$lang->story->create = "Create"; +$lang->story->createCase = "Create case"; +$lang->story->batchCreate = "Batch"; +$lang->story->change = "Change"; +$lang->story->changed = 'Changed'; +$lang->story->review = 'Review'; +$lang->story->edit = "Edit"; +$lang->story->close = 'Close'; +$lang->story->batchClose = 'Batch close'; +$lang->story->activate = 'Activate'; +$lang->story->delete = "Delete"; +$lang->story->view = "Info"; +$lang->story->tasks = "Tasks"; +$lang->story->taskCount = 'Tasks count'; +$lang->story->bugs = "Bug"; +$lang->story->linkStory = 'Related story'; +$lang->story->export = "Export"; +$lang->story->reportChart = "Report"; + +$lang->story->common = 'Story'; +$lang->story->id = 'ID'; +$lang->story->product = 'Product'; +$lang->story->module = 'Module'; +$lang->story->source = 'Source'; +$lang->story->fromBug = 'From bug'; +$lang->story->release = 'Release'; +$lang->story->bug = 'Related Bug'; +$lang->story->title = 'Title'; +$lang->story->spec = 'Spec'; +$lang->story->verify = 'Verify'; +$lang->story->type = 'Type '; +$lang->story->pri = 'Priority'; +$lang->story->estimate = 'Estimate'; +$lang->story->estimateAB = 'Estimate'; +$lang->story->hour = 'Hour'; +$lang->story->status = 'Status'; +$lang->story->stage = 'Stage'; +$lang->story->stageAB = 'Stage'; +$lang->story->mailto = 'Mailto'; +$lang->story->openedBy = 'Opened by'; +$lang->story->openedDate = 'Opened date'; +$lang->story->assignedTo = 'Assigned to'; +$lang->story->assignedDate = 'Assigned date'; +$lang->story->lastEditedBy = 'Last edited by'; +$lang->story->lastEditedDate = 'Last edited date'; +$lang->story->lastEdited = 'Last edited'; +$lang->story->closedBy = 'Closed by'; +$lang->story->closedDate = 'Closed date'; +$lang->story->closedReason = 'Closed reason'; +$lang->story->rejectedReason = 'Reject reason'; +$lang->story->reviewedBy = 'Reviewed by'; +$lang->story->reviewedDate = 'Reviewed date'; +$lang->story->version = 'Version'; +$lang->story->project = 'Project'; +$lang->story->plan = 'Plan'; +$lang->story->planAB = 'Plan'; +$lang->story->comment = 'Comment'; +$lang->story->linkStories = 'Related story'; +$lang->story->childStories = 'Child story'; +$lang->story->duplicateStory = 'Duplicate story'; +$lang->story->reviewResult = 'Reviewed result'; +$lang->story->preVersion = 'Pre version'; +$lang->story->keywords = 'Keyword'; +$lang->story->newStory = 'Continue to add story.'; + +$lang->story->same = 'The same as above'; + +$lang->story->useList[0] = 'No use'; +$lang->story->useList[1] = 'Use'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = 'Draft'; +$lang->story->statusList['active'] = 'Active'; +$lang->story->statusList['closed'] = 'Closed'; +$lang->story->statusList['changed'] = 'Changed'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = 'Waitting'; +$lang->story->stageList['planned'] = 'Planned'; +$lang->story->stageList['projected'] = 'Projected'; +$lang->story->stageList['developing'] = 'Developing'; +$lang->story->stageList['developed'] = 'Developed'; +$lang->story->stageList['testing'] = 'Testing'; +$lang->story->stageList['tested'] = 'Tested'; +$lang->story->stageList['verified'] = 'Verified'; +$lang->story->stageList['released'] = 'Released'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = 'Done'; +$lang->story->reasonList['subdivided'] = 'Subdivided'; +$lang->story->reasonList['duplicate'] = 'Duplicate'; +$lang->story->reasonList['postponed'] = 'Postponed'; +$lang->story->reasonList['willnotdo'] = "Won't do"; +$lang->story->reasonList['cancel'] = 'Canceled'; +$lang->story->reasonList['bydesign'] = 'By design'; +//$lang->story->reasonList['isbug'] = '是个Bug'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = 'Pass'; +$lang->story->reviewResultList['revert'] = 'Revert'; +$lang->story->reviewResultList['clarify'] = 'Clarify'; +$lang->story->reviewResultList['reject'] = 'Reject'; + +$lang->story->reviewList[0] = 'No'; +$lang->story->reviewList[1] = 'Yes'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = 'Customer'; +$lang->story->sourceList['user'] = 'User'; +$lang->story->sourceList['po'] = 'Product Owner'; +$lang->story->sourceList['market'] = 'Market'; +$lang->story->sourceList['service'] = 'Customer service'; +$lang->story->sourceList['competitor'] = 'Competitor'; +$lang->story->sourceList['partner'] = 'Partner'; +$lang->story->sourceList['dev'] = 'Developer'; +$lang->story->sourceList['tester'] = 'Tester'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['other'] = 'Other'; + +$lang->story->priList[] = ''; +$lang->story->priList[3] = '3'; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[4] = '4'; + +$lang->story->legendBasicInfo = 'Basic info'; +$lang->story->legendLifeTime = 'Life time'; +$lang->story->legendRelated = 'Related info'; +$lang->story->legendMailto = 'Maitto'; +$lang->story->legendAttatch = 'Files'; +$lang->story->legendProjectAndTask = 'Project & task'; +$lang->story->legendBugs = 'Related Bug'; +$lang->story->legendFromBug = 'From Bug'; +$lang->story->legendCases = 'Related Case'; +$lang->story->legendLinkStories = 'Related story'; +$lang->story->legendChildStories = 'Child story'; +$lang->story->legendSpec = 'Spec'; +$lang->story->legendVerify = 'Verify standard'; +$lang->story->legendHistory = 'History'; +$lang->story->legendVersion = 'Versions'; +$lang->story->legendMisc = 'Misc'; + +$lang->story->lblChange = 'Change'; +$lang->story->lblReview = 'Review'; +$lang->story->lblActivate = 'Activate'; +$lang->story->lblClose = 'Close'; + +$lang->story->affectedProjects = 'Affected projects'; +$lang->story->affectedBugs = 'Affected bugs'; +$lang->story->affectedCases = 'Affected cases'; + +$lang->story->specTemplate = "Recommend template::As <a type of user>,I want <some goals>,so that <some reason>."; +$lang->story->notes = "(notes:if the title is empty, it is no use)"; +$lang->story->needNotReview = "needn't review"; +$lang->story->afterSubmit = "After created"; +$lang->story->successSaved = "Successfully saved"; +$lang->story->confirmDelete = "Are you sure to delete this story?"; +$lang->story->confirmBatchClose = "Are you sure to close those stories?"; +$lang->story->errorFormat = 'Error format'; +$lang->story->errorEmptyTitle = "Title can't be empty"; +$lang->story->mustChooseResult = 'Must choose s result'; +$lang->story->mustChoosePreVersion = 'Must select an version to revert'; +$lang->story->ajaxGetProjectStories = 'API:Project stories'; +$lang->story->ajaxGetProductStories = 'API:Product stories'; + +$lang->story->form->titleNote = 'A word to briefly express story content'; +$lang->story->form->area = 'The story of their respective range'; +$lang->story->form->desc = 'Description and standards, what stories? How to acceptance?'; +$lang->story->form->resource = 'Allocation of resources, who completed? How long does it take?'; +$lang->story->form->file = 'Attachments, if the demand for related documents, please click here to upload.'; + +$lang->story->action->reviewed = array('main' => '$date, reviewed by $actor, result is $extra.', 'extra' => $lang->story->reviewResultList); +$lang->story->action->closed = array('main' => '$date, closed by $actor, reason is $extra.', 'extra' => $lang->story->reasonList); +$lang->story->action->linked2plan = array('main' => '$date, linked to plan $extra by $actor.'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, removed from $extra> by $actor'); +$lang->story->action->linked2project = array('main' => '$date, linked to project $extra by $actor.'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, removed from project $extra by $actor.'); + +/* Report*/ +$lang->story->report->common = 'Report'; +$lang->story->report->select = 'Select'; +$lang->story->report->create = 'Create'; + +$lang->story->report->charts['storysPerProduct'] = 'Product storys'; +$lang->story->report->charts['storysPerModule'] = 'Module storys'; +$lang->story->report->charts['storysPerSource'] = 'Source storys'; +$lang->story->report->charts['storysPerPlan'] = 'Plan storys'; +$lang->story->report->charts['storysPerStatus'] = 'Sotrys of status'; +$lang->story->report->charts['storysPerStage'] = 'Storys of stage'; +$lang->story->report->charts['storysPerPri'] = 'Storys of priority'; +$lang->story->report->charts['storysPerEstimate'] = 'Storys of Estimate'; +$lang->story->report->charts['storysPerOpenedBy'] = 'Opened by user'; +$lang->story->report->charts['storysPerAssignedTo'] = 'Assigned to user'; +$lang->story->report->charts['storysPerClosedReason'] = 'Storys for reason'; +$lang->story->report->charts['storysPerChange'] = 'Story version'; + +$lang->story->report->options->swf = 'pie2d'; +$lang->story->report->options->width = 'auto'; +$lang->story->report->options->height = 300; +$lang->story->report->options->graph->baseFontSize = 12; +$lang->story->report->options->graph->showNames = 1; +$lang->story->report->options->graph->formatNumber = 1; +$lang->story->report->options->graph->decimalPrecision = 0; +$lang->story->report->options->graph->animation = 0; +$lang->story->report->options->graph->rotateNames = 0; +$lang->story->report->options->graph->yAxisName = 'COUNT'; +$lang->story->report->options->graph->pieRadius = 100; +$lang->story->report->options->graph->showColumnShadow = 0; + +$lang->story->report->storysPerProduct->graph->xAxisName = 'Product'; +$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; +$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; +$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; +$lang->story->report->storysPerStatus->graph->xAxisName = 'Status'; +$lang->story->report->storysPerStage->graph->xAxisName = 'Stage'; +$lang->story->report->storysPerPri->graph->xAxisName = 'Priority'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Opened by'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Assigned to'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Closed reason'; +$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimate'; +$lang->story->report->storysPerChange->graph->xAxisName = 'Change'; + +$lang->story->placeholder->estimate = 'Estimate the story point.'; +$lang->story->placeholder->mailto = 'Mail to'; diff --git a/trunk/module/story/lang/zh-cn.php b/trunk/module/story/lang/zh-cn.php new file mode 100644 index 0000000000..d26e125fbc --- /dev/null +++ b/trunk/module/story/lang/zh-cn.php @@ -0,0 +1,236 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->story->browse = "需求列表"; +$lang->story->create = "新增"; +$lang->story->createCase = "建用例"; +$lang->story->batchCreate = "批量添加"; +$lang->story->change = "变更"; +$lang->story->changed = '需求变更'; +$lang->story->review = '评审'; +$lang->story->edit = "编辑"; +$lang->story->close = '关闭'; +$lang->story->batchClose = '批量关闭'; +$lang->story->activate = '激活'; +$lang->story->delete = "删除"; +$lang->story->view = "需求详情"; +$lang->story->tasks = "相关任务"; +$lang->story->taskCount = '任务数'; +$lang->story->bugs = "Bug"; +$lang->story->linkStory = '关联需求'; +$lang->story->export = "导出"; +$lang->story->reportChart = "统计报表"; + +$lang->story->common = '需求'; +$lang->story->id = '编号'; +$lang->story->product = '所属产品'; +$lang->story->module = '所属模块'; +$lang->story->source = '来源'; +$lang->story->fromBug = '来源Bug'; +$lang->story->release = '发布计划'; +$lang->story->bug = '相关bug'; +$lang->story->title = '需求名称'; +$lang->story->spec = '需求描述'; +$lang->story->verify = '验收标准'; +$lang->story->type = '需求类型 '; +$lang->story->pri = '优先级'; +$lang->story->estimate = '预计工时'; +$lang->story->estimateAB = '预计'; +$lang->story->hour = '小时'; +$lang->story->status = '当前状态'; +$lang->story->stage = '所处阶段'; +$lang->story->stageAB = '阶段'; +$lang->story->mailto = '抄送给'; +$lang->story->openedBy = '由谁创建'; +$lang->story->openedDate = '创建日期'; +$lang->story->assignedTo = '指派给'; +$lang->story->assignedDate = '指派日期'; +$lang->story->lastEditedBy = '最后修改'; +$lang->story->lastEditedDate = '最后修改日期'; +$lang->story->lastEdited = '最后修改'; +$lang->story->closedBy = '由谁关闭'; +$lang->story->closedDate = '关闭日期'; +$lang->story->closedReason = '关闭原因'; +$lang->story->rejectedReason = '拒绝原因'; +$lang->story->reviewedBy = '由谁评审'; +$lang->story->reviewedDate = '评审时间'; +$lang->story->version = '版本号'; +$lang->story->project = '所属项目'; +$lang->story->plan = '所属计划'; +$lang->story->planAB = '计划'; +$lang->story->comment = '备注'; +$lang->story->linkStories = '相关需求'; +$lang->story->childStories = '细分需求'; +$lang->story->duplicateStory = '重复需求'; +$lang->story->reviewResult = '评审结果'; +$lang->story->preVersion = '之前版本'; +$lang->story->keywords = '关键词'; +$lang->story->newStory = '继续添加需求'; + +$lang->story->same = '同上'; + +$lang->story->useList[0] = '不使用'; +$lang->story->useList[1] = '使用'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = '草稿'; +$lang->story->statusList['active'] = '激活'; +$lang->story->statusList['closed'] = '已关闭'; +$lang->story->statusList['changed'] = '已变更'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = '未开始'; +$lang->story->stageList['planned'] = '已计划'; +$lang->story->stageList['projected'] = '已立项'; +$lang->story->stageList['developing'] = '研发中'; +$lang->story->stageList['developed'] = '研发完毕'; +$lang->story->stageList['testing'] = '测试中'; +$lang->story->stageList['tested'] = '测试完毕'; +$lang->story->stageList['verified'] = '已验收'; +$lang->story->stageList['released'] = '已发布'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = '已完成'; +$lang->story->reasonList['subdivided'] = '已细分'; +$lang->story->reasonList['duplicate'] = '重复'; +$lang->story->reasonList['postponed'] = '延期'; +$lang->story->reasonList['willnotdo'] = '不做'; +$lang->story->reasonList['cancel'] = '已取消'; +$lang->story->reasonList['bydesign'] = '设计如此'; +//$lang->story->reasonList['isbug'] = '是个Bug'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = '确认通过'; +$lang->story->reviewResultList['revert'] = '撤销变更'; +$lang->story->reviewResultList['clarify'] = '有待明确'; +$lang->story->reviewResultList['reject'] = '拒绝'; + +$lang->story->reviewList[0] = '否'; +$lang->story->reviewList[1] = '是'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = '客户'; +$lang->story->sourceList['user'] = '用户'; +$lang->story->sourceList['po'] = '产品经理'; +$lang->story->sourceList['market'] = '市场'; +$lang->story->sourceList['service'] = '客服'; +$lang->story->sourceList['competitor'] = '竞争对手'; +$lang->story->sourceList['partner'] = '合作伙伴'; +$lang->story->sourceList['dev'] = '开发人员'; +$lang->story->sourceList['tester'] = '测试人员'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['other'] = '其他'; + +$lang->story->priList[] = ''; +$lang->story->priList[3] = '3'; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[4] = '4'; + +$lang->story->legendBasicInfo = '基本信息'; +$lang->story->legendLifeTime = '需求的一生'; +$lang->story->legendRelated = '相关信息'; +$lang->story->legendMailto = '抄送给'; +$lang->story->legendAttatch = '附件'; +$lang->story->legendProjectAndTask = '项目任务'; +$lang->story->legendBugs = '相关Bug'; +$lang->story->legendFromBug = '来源Bug'; +$lang->story->legendCases = '相关用例'; +$lang->story->legendLinkStories = '相关需求'; +$lang->story->legendChildStories = '细分需求'; +$lang->story->legendSpec = '需求描述'; +$lang->story->legendVerify = '验收标准'; +$lang->story->legendHistory = '历史记录'; +$lang->story->legendVersion = '历史版本'; +$lang->story->legendMisc = '其他相关'; + +$lang->story->lblChange = '变更需求'; +$lang->story->lblReview = '评审需求'; +$lang->story->lblActivate = '激活需求'; +$lang->story->lblClose = '关闭需求'; + +$lang->story->affectedProjects = '影响的项目'; +$lang->story->affectedBugs = '影响的Bug'; +$lang->story->affectedCases = '影响的用例'; + +$lang->story->specTemplate = "建议参考的模板:作为一名<某种类型的用户>,我希望<达成某些目的>,这样可以<开发的价值>。"; +$lang->story->notes = '(注:如果“需求名称”为空,则表示不使用此行)'; +$lang->story->needNotReview = '不需要评审'; +$lang->story->afterSubmit = "添加之后"; +$lang->story->successSaved = "需求成功添加,"; +$lang->story->confirmDelete = "您确认删除该需求吗?"; +$lang->story->confirmBatchClose = "您确认关闭这些需求吗?"; +$lang->story->errorFormat = '需求数据有误'; +$lang->story->errorEmptyTitle = '标题不能为空'; +$lang->story->mustChooseResult = '必须选择评审结果'; +$lang->story->mustChoosePreVersion = '必须选择回溯的版本'; +$lang->story->ajaxGetProjectStories = '接口:获取项目需求列表'; +$lang->story->ajaxGetProductStories = '接口:获取产品需求列表'; + +$lang->story->form->titleNote = '一句话简要表达需求内容'; +$lang->story->form->area = '该需求所属范围'; +$lang->story->form->desc = '描述及标准,什么需求?如何验收?'; +$lang->story->form->resource = '资源分配,有谁完成?需要多少时间?'; +$lang->story->form->file = '附件,如果该需求有相关文件,请点此上传。'; + +$lang->story->action->reviewed = array('main' => '$date, 由 $actor 记录评审结果,结果为 $extra。', 'extra' => $lang->story->reviewResultList); +$lang->story->action->closed = array('main' => '$date, 由 $actor 关闭,原因为 $extra。', 'extra' => $lang->story->reasonList); +$lang->story->action->linked2plan = array('main' => '$date, 由 $actor 关联到计划 $extra。'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 从计划 $extra 移除。'); +$lang->story->action->linked2project = array('main' => '$date, 由 $actor 关联到项目 $extra。'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 从项目 $extra 移除。'); + +/* 统计报表。*/ +$lang->story->report->common = '报表'; +$lang->story->report->select = '请选择报表类型'; +$lang->story->report->create = '生成报表'; + +$lang->story->report->charts['storysPerProduct'] = '产品需求数量'; +$lang->story->report->charts['storysPerModule'] = '模块需求数量'; +$lang->story->report->charts['storysPerSource'] = '需求来源统计'; +$lang->story->report->charts['storysPerPlan'] = '计划进行统计'; +$lang->story->report->charts['storysPerStatus'] = '状态进行统计'; +$lang->story->report->charts['storysPerStage'] = '所处阶段进行统计'; +$lang->story->report->charts['storysPerPri'] = '优先级进行统计'; +$lang->story->report->charts['storysPerEstimate'] = '预计工时进行统计'; +$lang->story->report->charts['storysPerOpenedBy'] = '由谁创建来进行统计'; +$lang->story->report->charts['storysPerAssignedTo'] = '当前指派来进行统计'; +$lang->story->report->charts['storysPerClosedReason'] = '关闭原因来进行统计'; +$lang->story->report->charts['storysPerChange'] = '变更次数来进行统计'; + +$lang->story->report->options->swf = 'pie2d'; +$lang->story->report->options->width = 'auto'; +$lang->story->report->options->height = 300; +$lang->story->report->options->graph->baseFontSize = 12; +$lang->story->report->options->graph->showNames = 1; +$lang->story->report->options->graph->formatNumber = 1; +$lang->story->report->options->graph->decimalPrecision = 0; +$lang->story->report->options->graph->animation = 0; +$lang->story->report->options->graph->rotateNames = 0; +$lang->story->report->options->graph->yAxisName = 'COUNT'; +$lang->story->report->options->graph->pieRadius = 100; // 饼图直径。 +$lang->story->report->options->graph->showColumnShadow = 0; // 是否显示柱状图阴影。 + +$lang->story->report->storysPerProduct->graph->xAxisName = '产品'; +$lang->story->report->storysPerModule->graph->xAxisName = '模块'; +$lang->story->report->storysPerSource->graph->xAxisName = '来源'; +$lang->story->report->storysPerPlan->graph->xAxisName = '产品计划'; +$lang->story->report->storysPerStatus->graph->xAxisName = '状态'; +$lang->story->report->storysPerStage->graph->xAxisName = '所处阶段'; +$lang->story->report->storysPerPri->graph->xAxisName = '优先级'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由谁创建'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = '当前指派'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = '关闭原因'; +$lang->story->report->storysPerEstimate->graph->xAxisName = '预计时间'; +$lang->story->report->storysPerChange->graph->xAxisName = '变更次数'; + +$lang->story->placeholder->estimate = "完成该需求的工作量"; +$lang->story->placeholder->mailto = '输入用户名自动完成'; diff --git a/trunk/module/story/lang/zh-tw.php b/trunk/module/story/lang/zh-tw.php new file mode 100644 index 0000000000..262e9600be --- /dev/null +++ b/trunk/module/story/lang/zh-tw.php @@ -0,0 +1,236 @@ + + * @package story + * @version $Id: zh-tw.php 3431 2012-08-30 07:45:20Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +$lang->story->browse = "需求列表"; +$lang->story->create = "新增"; +$lang->story->createCase = "建用例"; +$lang->story->batchCreate = "批量添加"; +$lang->story->change = "變更"; +$lang->story->changed = '需求變更'; +$lang->story->review = '評審'; +$lang->story->edit = "編輯"; +$lang->story->close = '關閉'; +$lang->story->batchClose = '批量關閉'; +$lang->story->activate = '激活'; +$lang->story->delete = "刪除"; +$lang->story->view = "需求詳情"; +$lang->story->tasks = "相關任務"; +$lang->story->taskCount = '任務數'; +$lang->story->bugs = "Bug"; +$lang->story->linkStory = '關聯需求'; +$lang->story->export = "導出"; +$lang->story->reportChart = "統計報表"; + +$lang->story->common = '需求'; +$lang->story->id = '編號'; +$lang->story->product = '所屬產品'; +$lang->story->module = '所屬模組'; +$lang->story->source = '來源'; +$lang->story->fromBug = '來源Bug'; +$lang->story->release = '發佈計劃'; +$lang->story->bug = '相關bug'; +$lang->story->title = '需求名稱'; +$lang->story->spec = '需求描述'; +$lang->story->verify = '驗收標準'; +$lang->story->type = '需求類型 '; +$lang->story->pri = '優先順序'; +$lang->story->estimate = '預計工時'; +$lang->story->estimateAB = '預計'; +$lang->story->hour = '小時'; +$lang->story->status = '當前狀態'; +$lang->story->stage = '所處階段'; +$lang->story->stageAB = '階段'; +$lang->story->mailto = '抄送給'; +$lang->story->openedBy = '由誰創建'; +$lang->story->openedDate = '創建日期'; +$lang->story->assignedTo = '指派給'; +$lang->story->assignedDate = '指派日期'; +$lang->story->lastEditedBy = '最後修改'; +$lang->story->lastEditedDate = '最後修改日期'; +$lang->story->lastEdited = '最後修改'; +$lang->story->closedBy = '由誰關閉'; +$lang->story->closedDate = '關閉日期'; +$lang->story->closedReason = '關閉原因'; +$lang->story->rejectedReason = '拒絶原因'; +$lang->story->reviewedBy = '由誰評審'; +$lang->story->reviewedDate = '評審時間'; +$lang->story->version = '版本號'; +$lang->story->project = '所屬項目'; +$lang->story->plan = '所屬計劃'; +$lang->story->planAB = '計劃'; +$lang->story->comment = '備註'; +$lang->story->linkStories = '相關需求'; +$lang->story->childStories = '細分需求'; +$lang->story->duplicateStory = '重複需求'; +$lang->story->reviewResult = '評審結果'; +$lang->story->preVersion = '之前版本'; +$lang->story->keywords = '關鍵詞'; +$lang->story->newStory = '繼續添加需求'; + +$lang->story->same = '同上'; + +$lang->story->useList[0] = '不使用'; +$lang->story->useList[1] = '使用'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = '草稿'; +$lang->story->statusList['active'] = '激活'; +$lang->story->statusList['closed'] = '已關閉'; +$lang->story->statusList['changed'] = '已變更'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = '未開始'; +$lang->story->stageList['planned'] = '已計劃'; +$lang->story->stageList['projected'] = '已立項'; +$lang->story->stageList['developing'] = '研發中'; +$lang->story->stageList['developed'] = '研發完畢'; +$lang->story->stageList['testing'] = '測試中'; +$lang->story->stageList['tested'] = '測試完畢'; +$lang->story->stageList['verified'] = '已驗收'; +$lang->story->stageList['released'] = '已發佈'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = '已完成'; +$lang->story->reasonList['subdivided'] = '已細分'; +$lang->story->reasonList['duplicate'] = '重複'; +$lang->story->reasonList['postponed'] = '延期'; +$lang->story->reasonList['willnotdo'] = '不做'; +$lang->story->reasonList['cancel'] = '已取消'; +$lang->story->reasonList['bydesign'] = '設計如此'; +//$lang->story->reasonList['isbug'] = '是個Bug'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = '確認通過'; +$lang->story->reviewResultList['revert'] = '撤銷變更'; +$lang->story->reviewResultList['clarify'] = '有待明確'; +$lang->story->reviewResultList['reject'] = '拒絶'; + +$lang->story->reviewList[0] = '否'; +$lang->story->reviewList[1] = '是'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = '客戶'; +$lang->story->sourceList['user'] = '用戶'; +$lang->story->sourceList['po'] = '產品經理'; +$lang->story->sourceList['market'] = '市場'; +$lang->story->sourceList['service'] = '客服'; +$lang->story->sourceList['competitor'] = '競爭對手'; +$lang->story->sourceList['partner'] = '合作夥伴'; +$lang->story->sourceList['dev'] = '開發人員'; +$lang->story->sourceList['tester'] = '測試人員'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['other'] = '其他'; + +$lang->story->priList[] = ''; +$lang->story->priList[3] = '3'; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[4] = '4'; + +$lang->story->legendBasicInfo = '基本信息'; +$lang->story->legendLifeTime = '需求的一生'; +$lang->story->legendRelated = '相關信息'; +$lang->story->legendMailto = '抄送給'; +$lang->story->legendAttatch = '附件'; +$lang->story->legendProjectAndTask = '項目任務'; +$lang->story->legendBugs = '相關Bug'; +$lang->story->legendFromBug = '來源Bug'; +$lang->story->legendCases = '相關用例'; +$lang->story->legendLinkStories = '相關需求'; +$lang->story->legendChildStories = '細分需求'; +$lang->story->legendSpec = '需求描述'; +$lang->story->legendVerify = '驗收標準'; +$lang->story->legendHistory = '歷史記錄'; +$lang->story->legendVersion = '歷史版本'; +$lang->story->legendMisc = '其他相關'; + +$lang->story->lblChange = '變更需求'; +$lang->story->lblReview = '評審需求'; +$lang->story->lblActivate = '激活需求'; +$lang->story->lblClose = '關閉需求'; + +$lang->story->affectedProjects = '影響的項目'; +$lang->story->affectedBugs = '影響的Bug'; +$lang->story->affectedCases = '影響的用例'; + +$lang->story->specTemplate = "建議參考的模板:作為一名<某種類型的用戶>,我希望<達成某些目的>,這樣可以<開發的價值>。"; +$lang->story->notes = '(註:如果“需求名稱”為空,則表示不使用此行)'; +$lang->story->needNotReview = '不需要評審'; +$lang->story->afterSubmit = "添加之後"; +$lang->story->successSaved = "需求成功添加,"; +$lang->story->confirmDelete = "您確認刪除該需求嗎?"; +$lang->story->confirmBatchClose = "您確認關閉這些需求嗎?"; +$lang->story->errorFormat = '需求數據有誤'; +$lang->story->errorEmptyTitle = '標題不能為空'; +$lang->story->mustChooseResult = '必須選擇評審結果'; +$lang->story->mustChoosePreVersion = '必須選擇回溯的版本'; +$lang->story->ajaxGetProjectStories = '介面:獲取項目需求列表'; +$lang->story->ajaxGetProductStories = '介面:獲取產品需求列表'; + +$lang->story->form->titleNote = '一句話簡要表達需求內容'; +$lang->story->form->area = '該需求所屬範圍'; +$lang->story->form->desc = '描述及標準,什麼需求?如何驗收?'; +$lang->story->form->resource = '資源分配,有誰完成?需要多少時間?'; +$lang->story->form->file = '附件,如果該需求有相關檔案,請點此上傳。'; + +$lang->story->action->reviewed = array('main' => '$date, 由 $actor 記錄評審結果,結果為 $extra。', 'extra' => $lang->story->reviewResultList); +$lang->story->action->closed = array('main' => '$date, 由 $actor 關閉,原因為 $extra。', 'extra' => $lang->story->reasonList); +$lang->story->action->linked2plan = array('main' => '$date, 由 $actor 關聯到計劃 $extra。'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 從計劃 $extra 移除。'); +$lang->story->action->linked2project = array('main' => '$date, 由 $actor 關聯到項目 $extra。'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 從項目 $extra 移除。'); + +/* 統計報表。*/ +$lang->story->report->common = '報表'; +$lang->story->report->select = '請選擇報表類型'; +$lang->story->report->create = '生成報表'; + +$lang->story->report->charts['storysPerProduct'] = '產品需求數量'; +$lang->story->report->charts['storysPerModule'] = '模組需求數量'; +$lang->story->report->charts['storysPerSource'] = '需求來源統計'; +$lang->story->report->charts['storysPerPlan'] = '計划進行統計'; +$lang->story->report->charts['storysPerStatus'] = '狀態進行統計'; +$lang->story->report->charts['storysPerStage'] = '所處階段進行統計'; +$lang->story->report->charts['storysPerPri'] = '優先順序進行統計'; +$lang->story->report->charts['storysPerEstimate'] = '預計工時進行統計'; +$lang->story->report->charts['storysPerOpenedBy'] = '由誰創建來進行統計'; +$lang->story->report->charts['storysPerAssignedTo'] = '當前指派來進行統計'; +$lang->story->report->charts['storysPerClosedReason'] = '關閉原因來進行統計'; +$lang->story->report->charts['storysPerChange'] = '變更次數來進行統計'; + +$lang->story->report->options->swf = 'pie2d'; +$lang->story->report->options->width = 'auto'; +$lang->story->report->options->height = 300; +$lang->story->report->options->graph->baseFontSize = 12; +$lang->story->report->options->graph->showNames = 1; +$lang->story->report->options->graph->formatNumber = 1; +$lang->story->report->options->graph->decimalPrecision = 0; +$lang->story->report->options->graph->animation = 0; +$lang->story->report->options->graph->rotateNames = 0; +$lang->story->report->options->graph->yAxisName = 'COUNT'; +$lang->story->report->options->graph->pieRadius = 100; // 餅圖直徑。 +$lang->story->report->options->graph->showColumnShadow = 0; // 是否顯示柱狀圖陰影。 + +$lang->story->report->storysPerProduct->graph->xAxisName = '產品'; +$lang->story->report->storysPerModule->graph->xAxisName = '模組'; +$lang->story->report->storysPerSource->graph->xAxisName = '來源'; +$lang->story->report->storysPerPlan->graph->xAxisName = '產品計劃'; +$lang->story->report->storysPerStatus->graph->xAxisName = '狀態'; +$lang->story->report->storysPerStage->graph->xAxisName = '所處階段'; +$lang->story->report->storysPerPri->graph->xAxisName = '優先順序'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由誰創建'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = '當前指派'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = '關閉原因'; +$lang->story->report->storysPerEstimate->graph->xAxisName = '預計時間'; +$lang->story->report->storysPerChange->graph->xAxisName = '變更次數'; + +$lang->story->placeholder->estimate = "完成該需求的工作量"; +$lang->story->placeholder->mailto = '輸入用戶名自動完成'; diff --git a/trunk/module/story/model.php b/trunk/module/story/model.php new file mode 100644 index 0000000000..6d88fb68f7 --- /dev/null +++ b/trunk/module/story/model.php @@ -0,0 +1,1288 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> +dao->findById((int)$storyID)->from(TABLE_STORY)->fetch(); + if(!$story) return false; + if(substr($story->closedDate, 0, 4) == '0000') $story->closedDate = ''; + if($version == 0) $version = $story->version; + $spec = $this->dao->select('title,spec,verify')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWhere('version')->eq($version)->fetch(); + $story->title = isset($spec->title) ? $spec->title : ''; + $story->spec = isset($spec->spec) ? $spec->spec : ''; + $story->verify = isset($spec->verify) ? $spec->verify : ''; + + if($setImgSize) $story->spec = $this->loadModel('file')->setImgSize($story->spec); + if($setImgSize) $story->verify = $this->file->setImgSize($story->verify); + + $story->projects = $this->dao->select('t1.project, t2.name, t2.status') + ->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->where('t1.story')->eq($storyID) + ->orderBy('t1.project DESC') + ->fetchAll('project'); + $story->tasks = $this->dao->select('id, name, assignedTo, project, status, consumed, `left`')->from(TABLE_TASK)->where('story')->eq($storyID)->orderBy('id DESC')->fetchGroup('project'); + //$story->bugCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_BUG)->where('story')->eq($storyID)->fetch('count'); + //$story->caseCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_CASE)->where('story')->eq($storyID)->fetch('count'); + if($story->toBug) $story->toBugTitle = $this->dao->findById($story->toBug)->from(TABLE_BUG)->fetch('title'); + if($story->plan) $story->planTitle = $this->dao->findById($story->plan)->from(TABLE_PRODUCTPLAN)->fetch('title'); + $extraStories = array(); + if($story->duplicateStory) $extraStories = array($story->duplicateStory); + if($story->linkStories) $extraStories = explode(',', $story->linkStories); + if($story->childStories) $extraStories = array_merge($extraStories, explode(',', $story->childStories)); + $extraStories = array_unique($extraStories); + if(!empty($extraStories)) $story->extraStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($extraStories)->fetchPairs(); + return $story; + } + + /** + * Get affected things. + * + * @param object $story + * @access public + * @return object + */ + public function getAffectedScope($story) + { + /* Remove closed projects. */ + if($story->projects) + { + foreach($story->projects as $projectID => $project) if($project->status == 'done') unset($story->projects[$projectID]); + } + + /* Get team members. */ + if($story->projects) + { + $story->teams = $this->dao->select('account, project') + ->from(TABLE_TEAM) + ->where('project')->in(array_keys($story->projects)) + ->fetchGroup('project'); + } + + /* Get affected bugs. */ + $story->bugs = $this->dao->findByStory($story->id)->from(TABLE_BUG) + ->andWhere('status')->ne('closed') + ->andWhere('deleted')->eq(0) + ->orderBy('id desc')->fetchAll(); + + /* Get affected cases. */ + $story->cases = $this->dao->findByStory($story->id)->from(TABLE_CASE)->andWhere('deleted')->eq(0)->fetchAll(); + + return $story; + } + + /** + * Create a story. + * + * @access public + * @return int|bool the id of the created story or false when error. + */ + public function create($projectID = 0, $bugID = 0) + { + $now = helper::now(); + $story = fixer::input('post') + ->cleanInt('product,module,pri,plan') + ->cleanFloat('estimate') + ->stripTags('title') + ->callFunc('title', 'trim') + ->setDefault('plan', 0) + ->add('openedBy', $this->app->user->account) + ->add('openedDate', $now) + ->add('assignedDate', 0) + ->add('version', 1) + ->add('status', 'draft') + ->setIF($this->post->assignedTo != '', 'assignedDate', $now) + ->setIF($this->post->needNotReview, 'status', 'active') + ->setIF($this->post->plan > 0, 'stage', 'planned') + ->setIF($projectID > 0, 'stage', 'projected') + ->setIF($bugID > 0, 'fromBug', $bugID) + ->remove('files,labels,spec,verify,needNotReview,newStory') + ->get(); + + $this->dao->insert(TABLE_STORY)->data($story)->autoCheck()->batchCheck($this->config->story->create->requiredFields, 'notempty')->exec(); + if(!dao::isError()) + { + $storyID = $this->dao->lastInsertID(); + $this->loadModel('file')->saveUpload('story', $storyID, $extra = 1); + + $data->story = $storyID; + $data->version = 1; + $data->title = $story->title; + $data->spec = $this->post->spec; + $data->verify = $this->post->verify; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + + if($projectID != 0) + { + $this->dao->insert(TABLE_PROJECTSTORY) + ->set('company')->eq(1) + ->set('project')->eq($projectID) + ->set('product')->eq($this->post->product) + ->set('story')->eq($storyID) + ->set('version')->eq(1) + ->exec(); + } + + if($bugID > 0) + { + $bug->toStory = $storyID; + $bug->status = 'closed'; + $bug->resolution = 'tostory'; + $bug->resolvedBy = $this->app->user->account; + $bug->resolvedDate = $now; + $bug->closedBy = $this->app->user->account; + $bug->closedDate = $now; + $bug->assignedTo = 'closed'; + $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec(); + + $this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID); + $this->action->create('bug', $bugID, 'Closed'); + + /* add files to story from bug. */ + $files = $this->dao->select('*')->from(TABLE_FILE) + ->where('objectType')->eq('bug') + ->andWhere('objectID')->eq($bugID) + ->fetchAll(); + if(!empty($files)) + { + foreach($files as $file) + { + $file->objectType = 'story'; + $file->objectID = $storyID; + unset($file->id); + $this->dao->insert(TABLE_FILE)->data($file)->exec(); + } + } + } + return $storyID; + } + return false; + } + + /** + * Create a batch stories. + * + * @access public + * @return int|bool the id of the created story or false when error. + */ + public function batchCreate($productID = 0) + { + $now = helper::now(); + $stories = fixer::input('post')->get(); + for($i = 0; $i < $this->config->story->batchCreate; $i++) + { + if($stories->title[$i] != '') + { + $data[$i]->module = $stories->module[$i] != 'same' ? $stories->module[$i] : ($i == 0 ? 0 : $data[$i-1]->module); + $data[$i]->plan = $stories->plan[$i] == 'same' ? ($i != 0 ? $data[$i-1]->plan : 0) : ($stories->plan[$i] != '' ? $stories->plan[$i] : 0); + $data[$i]->title = $stories->title[$i]; + $data[$i]->pri = $stories->pri[$i] != '' ? $stories->pri[$i] : 0; + $data[$i]->estimate = $stories->estimate[$i] != '' ? $stories->estimate[$i] : 0; + $data[$i]->status = $stories->needReview[$i] == 0 ? 'active' : 'draft'; + $data[$i]->product = $productID; + $data[$i]->openedBy = $this->app->user->account; + $data[$i]->openedDate = $now; + $data[$i]->version = 1; + + $this->dao->insert(TABLE_STORY) + ->data($data[$i]) + ->autoCheck() + ->batchCheck($this->config->story->create->requiredFields, 'notempty') + ->exec(); + if(dao::isError()) + { + echo js::error(dao::getError()); + die(js::reload('parent')); + } + + $storyID = $this->dao->lastInsertID(); + + $specData[$i]->story = $storyID; + $specData[$i]->version = 1; + $specData[$i]->title = $stories->title[$i]; + if($stories->spec[$i] != '') $specData[$i]->spec = $stories->spec[$i]; + $this->dao->insert(TABLE_STORYSPEC)->data($specData[$i])->exec(); + + $this->loadModel('action'); + $actionID = $this->action->create('story', $storyID, 'Opened', ''); + $mails[$i]->storyID = $storyID; + $mails[$i]->actionID = $actionID; + } + else + { + unset($stories->use[$i]); + unset($stories->module[$i]); + unset($stories->plan[$i]); + unset($stories->title[$i]); + unset($stories->spec[$i]); + unset($stories->pri[$i]); + unset($stories->estimate[$i]); + unset($stories->needReview[$i]); + } + } + return $mails; + } + + /** + * Change a story. + * + * @param int $storyID + * @access public + * @return array the change of the story. + */ + public function change($storyID) + { + $specChanged = false; + $oldStory = $this->getById($storyID); + $newTitle = stripslashes($this->post->title); + $newSpec = stripslashes($this->post->spec); + $newVerify = stripslashes($this->post->verify); + if($newSpec != $oldStory->spec or $newVerify != $oldStory->verify or $newTitle != $oldStory->title or $this->loadModel('file')->getCount()) $specChanged = true; + + $now = helper::now(); + $story = fixer::input('post') + ->stripTags('title') + ->callFunc('title', 'trim') + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->setIF($this->post->assignedTo != $oldStory->assignedTo, 'assignedDate', $now) + ->setIF($specChanged, 'version', $oldStory->version + 1) + ->setIF($specChanged and $oldStory->status == 'active' and $this->post->needNotReview == false, 'status', 'changed') + ->setIF($specChanged and $oldStory->status == 'draft' and $this->post->needNotReview, 'status', 'active') + ->setIF($specChanged, 'reviewedBy', '') + ->setIF($specChanged, 'closedBy', '') + ->setIF($specChanged, 'closedReason', '') + ->setIF($specChanged and $oldStory->reviewedBy, 'reviewedDate', '0000-00-00') + ->setIF($specChanged and $oldStory->closedBy, 'closedDate', '0000-00-00') + ->remove('files,labels,spec,verify,comment,needNotReview') + ->get(); + $this->dao->update(TABLE_STORY) + ->data($story) + ->autoCheck() + ->batchCheck($this->config->story->change->requiredFields, 'notempty') + ->where('id')->eq((int)$storyID)->exec(); + if(!dao::isError()) + { + if($specChanged) + { + $data->story = $storyID; + $data->version = $oldStory->version + 1; + $data->title = $story->title; + $data->spec = $this->post->spec; + $data->verify = $this->post->verify; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + $story->spec = $this->post->spec; + $story->verify = $this->post->verify; + } + else + { + unset($oldStory->spec); + } + return common::createChanges($oldStory, $story); + } + } + + /** + * Update a story. + * + * @param int $storyID + * @access public + * @return array the changes of the story. + */ + public function update($storyID) + { + $now = helper::now(); + $oldStory = $this->getById($storyID); + + $story = fixer::input('post') + ->cleanInt('product,module,pri,plan') + ->stripTags('title') + ->add('assignedDate', $oldStory->assignedDate) + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->setDefault('status', $oldStory->status) + ->setIF($this->post->plan !== false and $this->post->plan == '', 'plan', 0) + ->setIF($this->post->assignedTo != $oldStory->assignedTo, 'assignedDate', $now) + ->setIF($this->post->closedBy != false and $oldStory->closedDate == '', 'closedDate', $now) + ->setIF($this->post->closedReason != false and $oldStory->closedDate == '', 'closedDate', $now) + ->setIF($this->post->closedBy != false or $this->post->closedReason != false, 'status', 'closed') + ->setIF($this->post->closedReason != false and $this->post->closedBy == false, 'closedBy', $this->app->user->account) + ->remove('files,labels,comment') + ->get(); + + $this->dao->update(TABLE_STORY) + ->data($story) + ->autoCheck() + ->batchCheck($this->config->story->edit->requiredFields, 'notempty') + ->checkIF(isset($story->closedBy), 'closedReason', 'notempty') + ->checkIF(isset($story->closedReason) and $story->closedReason == 'done', 'stage', 'notempty') + ->checkIF(isset($story->closedReason) and $story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkIF(isset($story->closedReason) and $story->closedReason == 'subdivided', 'childStories', 'notempty') + ->where('id')->eq((int)$storyID)->exec(); + + if(!dao::isError()) return common::createChanges($oldStory, $story); + } + + /** + * Review a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function review($storyID) + { + if($this->post->result == false) die(js::alert($this->lang->story->mustChooseResult)); + if($this->post->result == 'revert' and $this->post->preVersion == false) die(js::alert($this->lang->story->mustChoosePreVersion)); + + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $date = helper::today(); + $story = fixer::input('post') + ->remove('result,preVersion,comment') + ->setDefault('reviewedDate', $date) + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->setIF($this->post->result == 'pass' and $oldStory->status == 'draft', 'status', 'active') + ->setIF($this->post->result == 'pass' and $oldStory->status == 'changed', 'status', 'active') + ->setIF($this->post->result == 'reject', 'closedBy', $this->app->user->account) + ->setIF($this->post->result == 'reject', 'closedDate', $now) + ->setIF($this->post->result == 'reject', 'assignedTo', 'closed') + ->setIF($this->post->result == 'reject', 'status', 'closed') + ->setIF($this->post->result == 'revert', 'version', $this->post->preVersion) + ->setIF($this->post->result == 'revert', 'status', 'active') + ->setIF($this->post->closedReason == 'done', 'stage', 'released') + ->removeIF($this->post->result != 'reject', 'closedReason, duplicateStory, childStories') + ->removeIF($this->post->result == 'reject' and $this->post->closedReason != 'duplicate', 'duplicateStory') + ->removeIF($this->post->result == 'reject' and $this->post->closedReason != 'subdivided', 'childStories') + ->get(); + $this->dao->update(TABLE_STORY)->data($story) + ->autoCheck() + ->batchCheck($this->config->story->review->requiredFields, 'notempty') + ->checkIF($this->post->result == 'reject', 'closedReason', 'notempty') + ->checkIF($this->post->result == 'reject' and $this->post->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkIF($this->post->result == 'reject' and $this->post->closedReason == 'subdivided', 'childStories', 'notempty') + ->where('id')->eq($storyID)->exec(); + if($this->post->result == 'revert') + { + $preTitle = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($this->post->preVersion)->fetch('title'); + $this->dao->update(TABLE_STORY)->set('title')->eq($preTitle)->where('id')->eq($storyID)->exec(); + $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($oldStory->version)->exec(); + $this->dao->delete()->from(TABLE_FILE)->where('objectType')->eq('story')->andWhere('objectID')->eq($storyID)->andWhere('extra')->eq($oldStory->version)->exec(); + } + $this->setStage($storyID); + return true; + } + + /** + * Close a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function close($storyID) + { + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $story = fixer::input('post') + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->add('closedDate', $now) + ->add('closedBy', $this->app->user->account) + ->add('assignedTo', 'closed') + ->add('assignedDate', $now) + ->add('status', 'closed') + ->removeIF($this->post->closedReason != 'duplicate', 'duplicateStory') + ->removeIF($this->post->closedReason != 'subdivided', 'childStories') + ->setIF($this->post->closedReason == 'done', 'stage', 'released') + ->setIF($this->post->closedReason != 'done', 'plan', 0) + ->remove('comment') + ->get(); + $this->dao->update(TABLE_STORY)->data($story) + ->autoCheck() + ->batchCheck($this->config->story->close->requiredFields, 'notempty') + ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkIF($story->closedReason == 'subdivided', 'childStories', 'notempty') + ->where('id')->eq($storyID)->exec(); + return common::createChanges($oldStory, $story); + } + + /** + * Batch close story. + * + * @access public + * @return void + */ + public function batchClose() + { + /* Init vars. */ + $stories = array(); + $allChanges = array(); + $now = helper::now(); + $storyIDList = $this->post->storyIDList ? $this->post->storyIDList : array(); + + /* Adjust whether the post data is complete, if not, remove the last element of $storyIDList. */ + if($this->session->showSuhosinInfo) array_pop($storyIDList); + if(!empty($storyIDList)) + { + foreach($storyIDList as $storyID) + { + $oldStory = $this->getById($storyID); + + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->closedBy = $this->app->user->account; + $story->closedDate = $now; + $story->assignedTo = 'closed'; + $story->assignedDate = $now; + $story->status = 'closed'; + + $story->closedReason = $this->post->closedReasons[$storyID]; + $story->duplicateStory = $this->post->duplicateStoryIDList[$storyID] ? $this->post->duplicateStoryIDList[$storyID] : $oldStory->duplicateStory; + $story->childStories = $this->post->childStoriesIDList[$storyID] ? $this->post->childStoriesIDList[$storyID] : $oldStory->childStories; + + if($story->closedReason == 'done') $story->stage = 'released'; + if($story->closedReason != 'done') $story->plan = 0; + + $stories[$storyID] = $story; + unset($story); + } + + foreach($stories as $storyID => $story) + { + $oldStory = $this->getById($storyID); + + $this->dao->update(TABLE_STORY)->data($story) + ->autoCheck() + ->batchCheck($this->config->story->close->requiredFields, 'notempty') + ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkIF($story->closedReason == 'subdivided', 'childStories', 'notempty') + ->where('id')->eq($storyID)->exec(); + + if(!dao::isError()) + { + $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + else + { + die(js::error('story#' . $storyID . dao::getError(true))); + } + } + } + + return $allChanges; + } + + /** + * Activate a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function activate($storyID) + { + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $story = fixer::input('post') + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->add('assignedDate', $now) + ->add('status', 'active') + ->add('closedBy', '') + ->add('closedReason', '') + ->add('closedDate', '0000-00-00') + ->add('reviewedBy', '') + ->add('reviewedDate', '0000-00-00') + ->remove('comment') + ->get(); + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq($storyID)->exec(); + return true; + } + + /** + * Set stage of a story. + * + * @param int $storyID + * @param string $customStage + * @access public + * @return bool + */ + public function setStage($storyID, $customStage = '') + { + /* Custom stage defined, use it. */ + if($customStage) + { + $this->dao->update(TABLE_STORY)->set('stage')->eq($customStage)->where('id')->eq((int)$storyID)->exec(); + return true; + } + + /* Get projects which status is doing. */ + $projects = $this->dao->select('project') + ->from(TABLE_PROJECTSTORY)->alias('t1')->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t1.story')->eq((int)$storyID) + ->andWhere('t2.status')->ne('done') + ->andWhere('t2.deleted')->eq(0) + ->fetchPairs(); + + /* If no projects, in plan, stage is planned. No plan, wait. */ + if(!$projects) + { + $this->dao->update(TABLE_STORY)->set('stage')->eq('wait')->where('id')->eq((int)$storyID)->andWhere('plan')->eq(0)->andWhere('status')->eq('active')->exec(); + $this->dao->update(TABLE_STORY)->set('stage')->eq('planned')->where('id')->eq((int)$storyID)->andWhere('plan')->gt(0)->exec(); + return true; + } + + /* Search related tasks. */ + $tasks = $this->dao->select('type,status')->from(TABLE_TASK) + ->where('project')->in($projects) + ->andWhere('story')->eq($storyID) + ->andWhere('type')->in('devel,test') + ->andWhere('status')->ne('cancel') + ->andWhere('deleted')->eq(0) + ->fetchGroup('type'); + + /* No tasks, then the stage is projected. */ + if(!$tasks) + { + $this->dao->update(TABLE_STORY)->set('stage')->eq('projected')->where('id')->eq((int)$storyID)->exec(); + return true; + } + + /* Get current stage and set as default value. */ + $currentStage = $this->dao->findById($storyID)->from(TABLE_STORY)->fields('stage')->fetch('stage'); + $stage = $currentStage; + + /* Cycle all tasks, get counts of every type and every status. */ + $statusList['devel'] = array('wait' => 0, 'doing' => 0, 'done' => 0); + $statusList['test'] = array('wait' => 0, 'doing' => 0, 'done' => 0); + foreach($tasks as $type => $typeTasks) + { + foreach($typeTasks as $task) + { + $status = $task->status ? $task->status : 'wait'; + $status = $status == 'closed' ? 'done' : $status; + + $statusList[$task->type][$status] ++; + } + } + + /* Get counts of every type tasks. */ + $develTasks = isset($tasks['devel']) ? count($tasks['devel']) : 0; + $testTasks = isset($tasks['test']) ? count($tasks['test']) : 0; + + /** + * Judge stage according to the devel and test tasks' status. + * + * 1. one doing devel task, all test tasks waiting, set stage as developing. + * 2. all devel tasks done, all test tasks waiting, set stage as developed. + * 3. one test task doing, set stage as testing. + * 4. all test tasks done, still some devel tasks not done(wait, doing), set stage as testing. + * 5. all test tasks done, all devel tasks done, set stage as tested. + */ + if($statusList['devel']['doing'] > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developing'; + if($statusList['devel']['done'] == $develTasks and $develTasks > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developed'; + if($statusList['test']['doing'] > 0) $stage = 'testing'; + if(($statusList['devel']['wait'] > 0 or $statusList['devel']['doing'] > 0) and $statusList['test']['done'] == $testTasks and $testTasks > 0) $stage = 'testing'; + if($statusList['devel']['done'] == $develTasks and $develTasks > 0 and $statusList['test']['done'] == $testTasks and $testTasks > 0) $stage = 'tested'; + + $this->dao->update(TABLE_STORY)->set('stage')->eq($stage)->where('id')->eq((int)$storyID)->exec(); + return; + } + + /** + * Get stories list of a product. + * + * @param int $productID + * @param array|string $moduleIds + * @param string $status + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getProductStories($productID = 0, $moduleIds = 0, $status = 'all', $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('t1.*, t2.title as planTitle') + ->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_PRODUCTPLAN)->alias('t2')->on('t1.plan = t2.id') + ->where('t1.product')->in($productID) + ->beginIF(!empty($moduleIds))->andWhere('module')->in($moduleIds)->fi() + ->beginIF($status != 'all')->andWhere('status')->in($status)->fi() + ->andWhere('t1.deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + + /** + * Get stories pairs of a product. + * + * @param int $productID + * @param array|string $moduleIds + * @param string $status + * @param string $order + * @access public + * @return array + */ + public function getProductStoryPairs($productID = 0, $moduleIds = 0, $status = 'all', $order = 'id_desc') + { + $stories = $this->dao->select('t1.id, t1.title, t1.module, t1.pri, t1.estimate, t2.name AS product') + ->from(TABLE_STORY)->alias('t1')->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') + ->where('1=1') + ->beginIF($productID)->andWhere('t1.product')->in($productID)->fi() + ->beginIF($moduleIds)->andWhere('t1.module')->in($moduleIds)->fi() + ->beginIF($status != 'all')->andWhere('t1.status')->in($status)->fi() + ->andWhere('t1.deleted')->eq(0) + ->orderBy($order) + ->fetchAll(); + if(!$stories) return array(); + return $this->formatStories($stories); + } + + /** + * Get stories by assignedTo. + * + * @param int $productID + * @param string $account + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByAssignedTo($productID, $account, $orderBy, $pager) + { + return $this->getByField($productID, 'assignedTo', $account, $orderBy, $pager); + } + + /** + * Get stories by openedBy. + * + * @param int $productID + * @param string $account + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByOpenedBy($productID, $account, $orderBy, $pager) + { + return $this->getByField($productID, 'openedBy', $account, $orderBy, $pager); + } + + /** + * Get stories by reviewedBy. + * + * @param int $productID + * @param string $account + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByReviewedBy($productID, $account, $orderBy, $pager) + { + return $this->getByField($productID, 'reviewedBy', $account, $orderBy, $pager, 'include'); + } + + /** + * Get stories by closedBy. + * + * @param int $productID + * @param string $account + * @param string $orderBy + * @param object $pager + * @return array + */ + public function getByClosedBy($productID, $account, $orderBy, $pager) + { + return $this->getByField($productID, 'closedBy', $account, $orderBy, $pager); + } + + /** + * Get stories by status. + * + * @param int $productID + * @param string $orderBy + * @param object $pager + * @param string $status + * @access public + * @return array + */ + public function getByStatus($productID, $status, $orderBy, $pager) + { + return $this->getByField($productID, 'status', $status, $orderBy, $pager); + } + + /** + * Get stories by a field. + * + * @param int $productID + * @param string $fieldName + * @param mixed $fieldValue + * @param string $orderBy + * @param object $pager + * @param string $operator equal|include + * @access public + * @return array + */ + public function getByField($productID, $fieldName, $fieldValue, $orderBy, $pager, $operator = 'equal') + { + return $this->dao->select('t1.*, t2.title as planTitle') + ->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_PRODUCTPLAN)->alias('t2')->on('t1.plan = t2.id') + ->where('t1.product')->in($productID) + ->andWhere('t1.deleted')->eq(0) + ->beginIF($operator == 'equal')->andWhere($fieldName)->eq($fieldValue)->fi() + ->beginIF($operator == 'include')->andWhere($fieldName)->like("%$fieldValue%")->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + } + + /** + * Get stories through search. + * + * @access public + * @param int $productID + * @param int $queryID + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getBySearch($productID, $queryID, $orderBy, $pager, $projectID = '') + { + if($projectID != '') + { + $products = $this->loadModel('project')->getProducts($projectID); + } + else + { + $products = $this->loadModel('product')->getPairs(); + } + $query = $queryID ? $this->loadModel('search')->getQuery($queryID) : ''; + + /* Get the sql and form status from the query. */ + if($query) + { + $this->session->set('storyQuery', $query->sql); + $this->session->set('storyForm', $query->form); + } + if($this->session->storyQuery == false) $this->session->set('storyQuery', ' 1 = 1'); + + $allProduct = "`product` = 'all'"; + $storyQuery = $this->session->storyQuery; + $queryProductID = $productID; + if(strpos($this->session->storyQuery, $allProduct) !== false) + { + $storyQuery = str_replace($allProduct, '1', $this->session->storyQuery); + $queryProductID = 'all'; + } + $storyQuery = $storyQuery . 'AND `product`' . helper::dbIN(array_keys($products)); + + return $this->getBySQL($queryProductID, $storyQuery, $orderBy, $pager); + } + + /** + * Get stories by a sql. + * + * @param int $productID + * @param string $sql + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getBySQL($productID, $sql, $orderBy, $pager = null) + { + $tmpStories = $this->dao->select('*')->from(TABLE_STORY)->where($sql) + ->beginIF($productID != 'all' and $productID != '')->andWhere('product')->eq((int)$productID)->fi() + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + + if(!$tmpStories) return array(); + + /* Get plans. */ + $plans = array(); + foreach($tmpStories as $story) $plans[$story->plan] = $story->plan; + $plans = $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN)->where('id')->in(array_keys($plans))->fetchPairs(); + + /* Process plans. */ + $stories = array(); + foreach($tmpStories as $story) + { + $story->planTitle = isset($plans[$story->plan]) ? $plans[$story->plan] : ''; + $stories[] = $story; + } + return $stories; + } + + /** + * Get stories list of a project. + * + * @param int $projectID + * @param string $orderBy + * @access public + * @return array + */ + public function getProjectStories($projectID = 0, $orderBy = 'pri_asc,id_desc') + { + $stories = $this->dao->select('t1.*, t2.*')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->where('t1.project')->eq((int)$projectID) + ->andWhere('t2.deleted')->eq(0) + ->orderBy($orderBy) + ->fetchAll('id'); + return $stories; + } + + /** + * Get stories pairs of a project. + * + * @param int $projectID + * @param int $productID + * @access public + * @return array + */ + public function getProjectStoryPairs($projectID = 0, $productID = 0) + { + $stories = $this->dao->select('t2.id, t2.title, t2.module, t2.pri, t2.estimate, t3.name AS product') + ->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2') + ->on('t1.story = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3') + ->on('t1.product = t3.id') + ->where('t1.project')->eq((int)$projectID) + ->andWhere('t2.deleted')->eq(0) + ->beginIF($productID)->andWhere('t1.product')->eq((int)$productID)->fi() + ->fetchAll(); + if(!$stories) return array(); + return $this->formatStories($stories); + } + + /** + * Get stories list of a plan. + * + * @param int $planID + * @param string $status + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getPlanStories($planID, $status = 'all', $orderBy = 'id_desc', $pager = null) + { + $stories = $this->dao->select('*')->from(TABLE_STORY) + ->where('plan')->eq((int)$planID) + ->beginIF($status != 'all')->andWhere('status')->in($status)->fi() + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll('id'); + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story'); + + return $stories; + } + + /** + * Get stories pairs of a plan. + * + * @param int $planID + * @param string $status + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getPlanStoryPairs($planID, $status = 'all', $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('*')->from(TABLE_STORY) + ->where('plan')->eq($planID) + ->beginIF($status != 'all')->andWhere('status')->in($status)->fi() + ->andWhere('deleted')->eq(0) + ->fetchAll(); + } + + /** + * Get stories of a user. + * + * @param string $account + * @param string $type the query type + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getUserStories($account, $type = 'assignedto', $orderBy = 'id_desc', $pager = null) + { + $type = strtolower($type); + $stories = $this->dao->select('t1.*, t2.title as planTitle, t3.name as productTitle') + ->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_PRODUCTPLAN)->alias('t2')->on('t1.plan = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->where('t1.deleted')->eq(0) + ->beginIF($type == 'assignedto')->andWhere('assignedTo')->eq($this->app->user->account)->fi() + ->beginIF($type == 'openedby')->andWhere('openedby')->eq($this->app->user->account)->fi() + ->beginIF($type == 'reviewedby')->andWhere('reviewedby')->like('%' . $this->app->user->account . '%')->fi() + ->beginIF($type == 'closedby')->andWhere('closedby')->eq($this->app->user->account)->fi() + ->orderBy($orderBy)->page($pager)->fetchAll(); + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story'); + + return $stories; + } + + /** + * Get doing projects' members of a story. + * + * @param int $storyID + * @access public + * @return array + */ + public function getProjectMembers($storyID) + { + $projects = $this->dao->select('project') + ->from(TABLE_PROJECTSTORY)->alias('t1')->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t1.story')->eq((int)$storyID) + ->andWhere('t2.status')->eq('doing') + ->andWhere('t2.deleted')->eq(0) + ->fetchPairs(); + if($projects) return($this->dao->select('account')->from(TABLE_TEAM)->where('project')->in($projects)->fetchPairs('account')); + } + + /** + * Get version of a story. + * + * @param int $storyID + * @access public + * @return int + */ + public function getVersion($storyID) + { + return $this->dao->select('version')->from(TABLE_STORY)->where('id')->eq((int)$storyID)->fetch('version'); + } + + /** + * Get versions of some stories. + * + * @param array|string story id list + * @access public + * @return array + */ + public function getVersions($storyID) + { + return $this->dao->select('id, version')->from(TABLE_STORY)->where('id')->in($storyID)->fetchPairs(); + } + + /** + * Format stories + * + * @param array $stories + * @access public + * @return void + */ + public function formatStories($stories) + { + /* Get module names of stories. */ + /*$modules = array(); + foreach($stories as $story) $modules[] = $story->module; + $moduleNames = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in($modules)->fetchPairs();*/ + + /* Format these stories. */ + $storyPairs = array('' => ' '); + foreach($stories as $story) $storyPairs[$story->id] = $story->id . ':' . $story->title . "({$this->lang->story->pri}:$story->pri, {$this->lang->story->estimate}: $story->estimate)"; + return $storyPairs; + } + + /** + * Extract accounts from some stories. + * + * @param array $stories + * @access public + * @return array + */ + public function extractAccountsFromList($stories) + { + $accounts = array(); + foreach($stories as $story) + { + if(!empty($story->openedBy)) $accounts[] = $story->openedBy; + if(!empty($story->assignedTo)) $accounts[] = $story->assignedTo; + if(!empty($story->closedBy)) $accounts[] = $story->closedBy; + if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy; + } + return array_unique($accounts); + } + + /** + * Extract accounts from a story. + * + * @param object $story + * @access public + * @return array + */ + public function extractAccountsFromSingle($story) + { + $accounts = array(); + if(!empty($story->openedBy)) $accounts[] = $story->openedBy; + if(!empty($story->assignedTo)) $accounts[] = $story->assignedTo; + if(!empty($story->closedBy)) $accounts[] = $story->closedBy; + if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy; + return array_unique($accounts); + } + + /** + * Merge the default chart settings and the settings of current chart. + * + * @param string $chartType + * @access public + * @return void + */ + public function mergeChartOption($chartType) + { + $chartOption = $this->lang->story->report->$chartType; + $commonOption = $this->lang->story->report->options; + + $chartOption->graph->caption = $this->lang->story->report->charts[$chartType]; + if(!isset($chartOption->swf)) $chartOption->swf = $commonOption->swf; + if(!isset($chartOption->width)) $chartOption->width = $commonOption->width; + if(!isset($chartOption->height)) $chartOption->height = $commonOption->height; + + foreach($commonOption->graph as $key => $value) if(!isset($chartOption->graph->$key)) $chartOption->graph->$key = $value; + } + + /** + * Get report data of storys per product + * + * @access public + * @return array + */ + public function getDataOfStorysPerProduct() + { + $datas = $this->dao->select('product as name, count(product) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('product')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + $products = $this->loadModel('product')->getPairs(); + foreach($datas as $productID => $data) $data->name = isset($products[$productID]) ? $products[$productID] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per module + * + * @access public + * @return array + */ + public function getDataOfStorysPerModule() + { + $datas = $this->dao->select('module as name, count(module) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('module')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + $modules = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in(array_keys($datas))->fetchPairs(); + foreach($datas as $moduleID => $data) $data->name = isset($modules[$moduleID]) ? $modules[$moduleID] : '/'; + return $datas; + } + + /** + * Get report data of storys per source + * + * @access public + * @return array + */ + public function getDataOfStorysPerSource() + { + $datas = $this->dao->select('source as name, count(source) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('source')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + $this->lang->story->sourceList[''] = $this->lang->report->undefined; + foreach($datas as $key => $data) $data->name = isset($this->lang->story->sourceList[$key]) ? $this->lang->story->sourceList[$key] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per plan + * + * @access public + * @return array + */ + public function getDataOfStorysPerPlan() + { + $datas = $this->dao->select('plan as name, count(plan) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('plan')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + $plans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in(array_keys($datas))->fetchPairs(); + foreach($datas as $planID => $data) $data->name = isset($plans[$planID]) ? $plans[$planID] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per status + * + * @access public + * @return array + */ + public function getDataOfStorysPerStatus() + { + $datas = $this->dao->select('status as name, count(status) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('status')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $status => $data) if(isset($this->lang->story->statusList[$status])) $data->name = $this->lang->story->statusList[$status]; + return $datas; + } + + /** + * Get report data of storys per stage + * + * @access public + * @return array + */ + public function getDataOfStorysPerStage() + { + $datas = $this->dao->select('stage as name, count(stage) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('stage')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $stage => $data) $data->name = $this->lang->story->stageList[$stage] != '' ? $this->lang->story->stageList[$stage] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per pri + * + * @access public + * @return array + */ + public function getDataOfStorysPerPri() + { + $datas = $this->dao->select('pri as name, count(pri) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('pri')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $pri => $data) $data->name = $this->lang->story->priList[$pri] != '' ? $this->lang->story->priList[$pri] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per estimate + * + * @access public + * @return array + */ + public function getDataOfStorysPerEstimate() + { + return $this->dao->select('estimate as name, count(estimate) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('estimate')->orderBy('value')->fetchAll(); + } + + /** + * Get report data of storys per openedBy + * + * @access public + * @return array + */ + public function getDataOfStorysPerOpenedBy() + { + $datas = $this->dao->select('openedBy as name, count(openedBy) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('openedBy')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); + foreach($datas as $account => $data) $data->name = isset($this->users[$account]) ? $this->users[$account] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per assignedTo + * + * @access public + * @return array + */ + public function getDataOfStorysPerAssignedTo() + { + $datas = $this->dao->select('assignedTo as name, count(assignedTo) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('assignedTo')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); + foreach($datas as $account => $data) $data->name = (isset($this->users[$account]) and $this->users[$account] != '') ? $this->users[$account] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per closedReason + * + * @access public + * @return array + */ + public function getDataOfStorysPerClosedReason() + { + $datas = $this->dao->select('closedReason as name, count(closedReason) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('closedReason')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $reason => $data) $data->name = $this->lang->story->reasonList[$reason] != '' ? $this->lang->story->reasonList[$reason] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per change + * + * @access public + * @return array + */ + public function getDataOfStorysPerChange() + { + return $this->dao->select('(version-1) as name, count(*) as value')->from(TABLE_STORY) + ->beginIF($this->session->storyQueryCondition != false)->where($this->session->storyQueryCondition)->fi() + ->groupBy('version')->orderBy('value')->fetchAll(); + } + + /** + * Adjust the action clickable. + * + * @param object $story + * @param string $action + * @access public + * @return void + */ + public function isClickable($story, $action) + { + $action = strtolower($action); + + if($action == 'change') return $story->status != 'closed'; + if($action == 'review') return $story->status == 'draft' or $story->status == 'changed'; + if($action == 'close') return $story->status != 'closed'; + if($action == 'activate') return $story->status == 'closed' and $story->closedReason == 'postponed'; + + return true; + } +} diff --git a/trunk/module/story/view/activate.html.php b/trunk/module/story/view/activate.html.php new file mode 100644 index 0000000000..ff0367d40b --- /dev/null +++ b/trunk/module/story/view/activate.html.php @@ -0,0 +1,36 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + +
      story->activate;?>
      story->assignedTo;?>closedBy, 'class="select-3"');?>
      story->comment;?>
      + goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id")); + ?> +
      + +
      + diff --git a/trunk/module/story/view/affected.html.php b/trunk/module/story/view/affected.html.php new file mode 100644 index 0000000000..66af39cae4 --- /dev/null +++ b/trunk/module/story/view/affected.html.php @@ -0,0 +1,95 @@ +
      + + + projects as $projectID => $project):?> + + + + + + +
      story->affectedProjects;?>
      $project->name
      "; ?>
      teams[$projectID] as $member) echo $users[$member->account] . ' ';?> + + + + + + + + + +
      task->id;?>task->name;?>task->assignedTo;?>task->status;?>task->consumed;?>task->left;?>
      + tasks[$projectID])):?> +
      + + tasks[$projectID] as $task):?> + + + + + + + + + +
      id;?>createLink('task', 'view', "taskID=$task->id"), $task->name, '_blank');?>assignedTo];?>task->statusList[$task->status];?>consumed;?>left;?>
      +
      + +
      +
      + +
      + + + + + + + + + + + +
      story->affectedBugs;?>
      bug->id;?>bug->title;?>bug->status;?>bug->openedBy;?>bug->resolvedBy;?>bug->resolution;?>bug->lastEditedBy;?>
      +
      + + bugs as $bug):?> + + + + + + + + + + +
      id;?>createLink('bug', 'view', "bugID=$bug->id"), $bug->title, '_blank');?>bug->statusList[$bug->status];?>openedBy];?>resolvedBy];?>bug->resolutionList[$bug->resolution];?>lastEditedBy];?>
      +
      +
      + +
      + + + + + + + + + +
      story->affectedCases;?>
      testcase->id;?>testcase->title;?>testcase->status;?>testcase->openedBy;?>testcase->lastEditedBy;?>
      +
      + + cases as $case):?> + + + + + + + + +
      id;?>createLink('testcase', 'view', "caseID=$case->id"), $case->title, '_blank');?>testcase->statusList[$case->status];?>openedBy];?>lastEditedBy];?>
      +
      +
      diff --git a/trunk/module/story/view/batchclose.html.php b/trunk/module/story/view/batchclose.html.php new file mode 100755 index 0000000000..2c901a14cd --- /dev/null +++ b/trunk/module/story/view/batchclose.html.php @@ -0,0 +1,59 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      "> + + + + + + + + + + + + + + + + status != 'closed'):?> + + + + + + + + + + + + +
      story->common . $lang->colon . $lang->story->batchClose;?>
      idAB;?> story->title;?> story->status;?>story->closedReason;?> story->comment;?>
      id . html::hidden("storyIDList[$story->id]", $story->id);?>title;?>story->statusList[$story->status];?> +
      + status == 'draft') unset($this->lang->story->reasonList['cancel']); + echo html::select("closedReasons[$story->id]", $lang->story->reasonList, $story->closedReason, "class=w-70px onchange=setDuplicateAndChild(this.value,$story->id)"); + ?> +
      +
      id;?>' closedReason != 'duplicate') echo "style='display:none'";?>> + id]", '', "class=w-30px placeholder='{$lang->idAB}'");?> +
      + +
      id;?>' closedReason != 'subdivided') echo "style='display:none'";?>> + id]", '', "class=w-30px placeholder='{$lang->idAB}'");?> +
      +
      id]", $lang->story->reasonList, $story->closedReason, 'class="w-60px" disabled="disabled"');?>id]", '', "rows='2' class='area-1'");?>
      +
      + diff --git a/trunk/module/story/view/batchcreate.html.php b/trunk/module/story/view/batchcreate.html.php new file mode 100644 index 0000000000..dae9e6d012 --- /dev/null +++ b/trunk/module/story/view/batchcreate.html.php @@ -0,0 +1,49 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + story->batchCreate; $i++):?> + + + + + + + + + + + + + + + + +
      story->product . $lang->colon . $lang->story->batchCreate;?>
      idAB;?>story->module;?>story->plan;?>story->title;?>story->spec;?>story->pri;?>story->estimate;?>story->review;?>
      *";?>story->priList, $pri, 'class=select-1');?>story->reviewList, 0, "class='text-1'");?>
      +
      story->notes;?>
      +
      +
      +
      + diff --git a/trunk/module/story/view/change.html.php b/trunk/module/story/view/change.html.php new file mode 100644 index 0000000000..8c88de5ead --- /dev/null +++ b/trunk/module/story/view/change.html.php @@ -0,0 +1,52 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      story->change;?>
      story->reviewedBy;?>assignedTo, 'class="select-3"') . html::checkbox('needNotReview', $lang->story->needNotReview, '', "id='needNotReview'");?>
      story->title;?>title, 'class="text-1"');?>
      story->spec;?>spec), 'rows=8 class="area-1"');?>
      story->specTemplate;?>
      story->verify;?>verify), 'rows=6 class="area-1"');?>
      story->comment;?>
      attatch;?>fetch('file', 'buildform', 'filecount=2');?>
      + +
      + goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id")); + ?> +
      + +
      + diff --git a/trunk/module/story/view/close.html.php b/trunk/module/story/view/close.html.php new file mode 100644 index 0000000000..c101eef0f2 --- /dev/null +++ b/trunk/module/story/view/close.html.php @@ -0,0 +1,42 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + + + + + +
      title;?>
      story->closedReason;?>story->reasonList, '', 'class=select-3 onchange="setStory(this.value)"');?>
      story->comment;?>
      + + goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id"));?> +
      + +
      + diff --git a/trunk/module/story/view/create.html.php b/trunk/module/story/view/create.html.php new file mode 100644 index 0000000000..8f2a02083f --- /dev/null +++ b/trunk/module/story/view/create.html.php @@ -0,0 +1,74 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      story->create;?>
      story->product;?> + + +
      story->plan;?>
      story->source;?>story->sourceList, $source, 'class=select-3');?>
      story->title;?>
      story->spec;?>
      story->specTemplate;?>
      story->verify;?>
      story->pri;?>story->priList, $pri, 'class=select-3');?>
      story->estimate;?>story->hour;?>
      story->reviewedBy;?>story->needNotReview, '', "id='needNotReview'");?>
      story->mailto;?>
      story->keywords;?>
      story->legendAttatch;?>fetch('file', 'buildform');?>
      +
      + diff --git a/trunk/module/story/view/edit.html.php b/trunk/module/story/view/edit.html.php new file mode 100644 index 0000000000..f3f7362bcb --- /dev/null +++ b/trunk/module/story/view/edit.html.php @@ -0,0 +1,139 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +
      +
      STORY #id . $lang->colon . $story->title;?>
      +
      +
      + + + + + + + +
      +
      + story->legendSpec;?> +
      spec;?>
      +
      +
      + story->comment;?> + +
      +
      + goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id")); + ?> +
      + +
      +
      + story->legendBasicInfo;?> + + + + + + + + + + + + + + + + + + + + + + status != 'draft'):?> + + + + + + + + + + + + + + + + + +
      story->product;?>product, 'class="select-1" onchange="loadProduct(this.value)";');?>
      story->module;?>module, 'class="select-1"');?>
      story->plan;?>plan, 'class=select-1');?>
      story->source;?>story->sourceList, $story->source, 'class=select-1');?>
      story->status;?>story->statusList[$story->status];?>
      story->stage;?>story->stageList, $story->stage, 'class=select-1');?>
      story->pri;?>story->priList, $story->pri, 'class=select-1');?>
      story->estimate;?>estimate, "class='text-1'");?>
      story->keywords;?>keywords, 'class=text-1');?>
      +
      +
      + story->legendLifeTime;?> + + + + + + + + + + reviewedBy):?> + + + + + + status == 'closed'):?> + + + + + + + + + +
      story->openedBy;?>openedBy];?>
      story->assignedTo;?>assignedTo, 'class="select-1"');?>
      story->reviewedBy;?>reviewedBy, 'class="area-1"');?>
      story->closedBy;?>closedBy, 'class="select-1"');?>
      story->closedReason;?>story->reasonList, $story->closedReason, 'class="select-1"');?>
      +
      + +
      + story->legendMisc;?> + + + + + + + + + + + + + + + + + +
      story->duplicateStory;?>duplicateStory, "class='text-1'");?>
      story->linkStories;?>linkStories, "class='text-1'");?>
      story->childStories;?>childStories, "class='text-1'");?>
      story->mailto;?>mailto, "class='area-1'");?>
      +
      +
      +
      + diff --git a/trunk/module/story/view/export.html.php b/trunk/module/story/view/export.html.php new file mode 100644 index 0000000000..c6680aa28f --- /dev/null +++ b/trunk/module/story/view/export.html.php @@ -0,0 +1,13 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + diff --git a/trunk/module/story/view/header.html.php b/trunk/module/story/view/header.html.php new file mode 100644 index 0000000000..9f942f721f --- /dev/null +++ b/trunk/module/story/view/header.html.php @@ -0,0 +1,16 @@ + + + diff --git a/trunk/module/story/view/report.html.php b/trunk/module/story/view/report.html.php new file mode 100644 index 0000000000..ceb248413e --- /dev/null +++ b/trunk/module/story/view/report.html.php @@ -0,0 +1,62 @@ + + * @package product + * @version $Id: report.html.php 1594 2011-03-13 07:27:55Z wwccss $ + * @link http://www.zentao.net + */ +?> + +
      +
      story->report->common;?>
      +
      goback); ?>
      +
      + + + + + + + +
      +
      story->report->select;?>
      +
      +
      + story->report->charts, $checkedCharts);?> + + +

      + story->report->create);?> +
      +
      + + + $chartContent):?> + + + + + +
      story->report->common;?>
      + + + + + + + $data):?> + + + + + + +
      report->item;?>report->value;?>report->percent;?>
      name;?>value;?>percent * 100) . '%';?>
      +
      +
      + + diff --git a/trunk/module/story/view/review.html.php b/trunk/module/story/view/review.html.php new file mode 100644 index 0000000000..554d6eaef3 --- /dev/null +++ b/trunk/module/story/view/review.html.php @@ -0,0 +1,68 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + status == 'changed' or ($story->status == 'draft' and $story->version > 1)):?> + + + + + + + + + + + + + + + + + + + + +
      title;?>
      story->reviewedDate;?>
      story->reviewResult;?>story->reviewResultList, '', 'class=select-3 onchange="switchShow(this.value)"');?>
      story->assignedTo;?>lastEditedBy ? $story->lastEditedBy : $story->openedBy, 'class=select-3');?>
      story->reviewedBy;?>user->account . ', ', 'class=text-1');?>
      story->comment;?>
      + + goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id"));?> +
      + + + + diff --git a/trunk/module/story/view/sendmail.html.php b/trunk/module/story/view/sendmail.html.php new file mode 100644 index 0000000000..e8c916179b --- /dev/null +++ b/trunk/module/story/view/sendmail.html.php @@ -0,0 +1,38 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + "; + } + ?> +
      + STORY #id . "=>$story->assignedTo " . html::a(common::getSysURL() . $this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> +
      +
      + story->legendSpec;?> +
      + spec, '
      $task
      + diff --git a/trunk/module/story/view/view.html.php b/trunk/module/story/view/view.html.php new file mode 100644 index 0000000000..01d2672b98 --- /dev/null +++ b/trunk/module/story/view/view.html.php @@ -0,0 +1,268 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +
      deleted) echo "class='deleted'";?>>STORY #id . ' ' . $story->title;?>
      +
      + session->storyList != false ? $app->session->storyList : $this->createLink('product', 'browse', "productID=$story->product&moduleID=$story->module"); + if(!$story->deleted) + { + ob_start(); + + if($this->story->isClickable($story, 'change')) common::printIcon('story', 'change', "storyID=$story->id"); + if($this->story->isClickable($story, 'review')) common::printIcon('story', 'review', "storyID=$story->id"); + if($this->story->isClickable($story, 'close')) common::printIcon('story', 'close', "storyID=$story->id"); + if($this->story->isClickable($story, 'activate')) common::printIcon('story', 'activate', "storyID=$story->id"); + common::printIcon('story', 'createCase', "productID=$story->product&moduleID=0&from=¶m=0&storyID=$story->id", '', 'button', 'createCase'); + + common::printDivider(); + common::printIcon('story', 'edit', "storyID=$story->id"); + common::printCommentIcon('story'); + common::printIcon('story', 'create', "productID=$story->product&moduleID=$story->module&storyID=$story->id", '', 'button', 'copy'); + common::printIcon('story', 'delete', "storyID=$story->id", '', 'button', '', 'hiddenwin'); + + common::printDivider(); + common::printRPN($browseLink, $preAndNext); + + $actionLinks = ob_get_contents(); + ob_clean(); + echo $actionLinks; + } + ?> +
      +
      + + + + + + +
      +
      + story->legendSpec;?> +
      spec;?>
      +
      +
      + story->legendVerify;?> +
      verify;?>
      +
      + fetch('file', 'printFiles', array('files' => $story->files, 'fieldset' => 'true'));?> + + + +
      + +
      + story->legendBasicInfo;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      story->product;?>product", $product->name);?> +
      story->module;?> + $module) + { + if(!common::printLink('product', 'browse', "productID=$story->product&browseType=byModule¶m=$module->id", $module->name)) echo $module->name; + if(isset($modulePath[$key + 1])) echo $lang->arrow; + } + } + ?> +
      story->plan;?>planTitle)) if(!common::printLink('productplan', 'view', "planID=$story->plan", $story->planTitle)) echo $story->planTitle;?> +
      story->source;?>story->sourceList[$story->source];?>
      story->status;?>story->statusList[$story->status];?>
      story->stage;?>story->stageList[$story->stage];?>
      story->pri;?>story->priList[$story->pri];?>
      story->estimate;?>estimate;?>
      story->keywords;?>keywords;?>
      +
      +
      + story->legendLifeTime;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      story->openedBy;?>openedBy] . $lang->at . $story->openedDate;?>
      story->assignedTo;?>assignedTo) echo $users[$story->assignedTo] . $lang->at . $story->assignedDate;?>
      story->reviewedBy;?>reviewedBy); foreach($reviewedBy as $account) echo ' ' . $users[trim($account)]; ?>
      story->reviewedDate;?>reviewedBy) echo $story->reviewedDate;?>
      story->closedBy;?>closedBy) echo $users[$story->closedBy] . $lang->at . $story->closedDate;?>
      story->closedReason;?> + closedReason) echo $lang->story->reasonList[$story->closedReason]; + if(isset($story->extraStories[$story->duplicateStory])) + { + echo html::a(inlink('view', "storyID=$story->duplicateStory"), '#' . $story->duplicateStory . ' ' . $story->extraStories[$story->duplicateStory]); + } + ?> +
      story->lastEditedBy;?>lastEditedBy) echo $users[$story->lastEditedBy] . $lang->at . $story->lastEditedDate;?>
      +
      + +
      + story->legendProjectAndTask;?> + + + + +
      + tasks as $projectTasks) + { + foreach($projectTasks as $task) + { + @$projectName = $story->projects[$task->project]->name; + echo html::a($this->createLink('project', 'browse', "projectID=$task->project"), $projectName); + echo '' . html::a($this->createLink('task', 'view', "taskID=$task->id"), "#$task->id $task->name") . '
      '; + } + } + ?> +
      +
      + +
      + story->legendBugs;?> + + + + +
      + ' . html::a($this->createLink('bug', 'view', "bugID=$bug->id"), "#$bug->id $bug->title") . '
      '; + } + ?> +
      +
      + +
      + story->legendCases;?> + + + + +
      + ' . html::a($this->createLink('testcase', 'view', "caseID=$case->id"), "#$case->id $case->title") . '
      '; + } + ?> +
      +
      + +
      + story->legendLinkStories;?> +
      + linkStories) ; + foreach($linkStories as $linkStoryID) + { + if(isset($story->extraStories[$linkStoryID])) echo html::a(inlink('view', "storyID=$linkStoryID"), "#$linkStoryID " . $story->extraStories[$linkStoryID]) . '
      '; + } + ?> +
      +
      +
      + story->legendChildStories;?> +
      + childStories) ; + foreach($childStories as $childStoryID) + { + if(isset($story->extraStories[$childStoryID])) echo html::a(inlink('view', "storyID=$childStoryID"), "#$childStoryID " . $story->extraStories[$childStoryID]) . '
      '; + } + ?> +
      +
      +
      + story->legendFromBug;?> +
      + ' . html::a($this->createLink('bug', 'view', "bugID=$fromBug->id"), "#$fromBug->id $fromBug->title") . '
      '; + ?> +
      +
      +
      + story->legendMailto;?> +
      mailto); foreach($mailto as $account) echo ' ' . $users[trim($account)]; ?>
      +
      +
      + story->legendVersion;?> +
      version; $i >= 1; $i --) echo html::a(inlink('view', "storyID=$story->id&version=$i"), "#$i");?>
      +
      +
      + diff --git a/trunk/module/svn/config.php b/trunk/module/svn/config.php new file mode 100644 index 0000000000..43ed023788 --- /dev/null +++ b/trunk/module/svn/config.php @@ -0,0 +1,31 @@ +svn->client = '/usr/bin/svn'; // c:\svn\svn.exe + * $config->svn->repos['pms']['path'] = 'http://svn.zentao.net/zentao/trunk/'; + * $config->svn->repos['pms']['username'] = 'user'; + * $config->svn->repos['pms']['password'] = 'pass'; + * + */ +$config->svn->encodings = 'utf-8, gbk'; +$config->svn->client = ''; + +$i = 1; +$config->svn->repos[$i]['path'] = ''; +$config->svn->repos[$i]['username'] = ''; +$config->svn->repos[$i]['password'] = ''; + +/* +$i ++; +$config->svn->repos[$i]['path'] = ''; +$config->svn->repos[$i]['username'] = ''; +$config->svn->repos[$i]['password'] = ''; +*/ diff --git a/trunk/module/svn/control.php b/trunk/module/svn/control.php new file mode 100644 index 0000000000..b5fa68504b --- /dev/null +++ b/trunk/module/svn/control.php @@ -0,0 +1,98 @@ + + * @package svn + * @version $Id$ + * @link http://www.zentao.net + */ +class svn extends control +{ + /** + * Sync svn. + * + * @access public + * @return void + */ + public function run() + { + $this->svn->run(); + } + + /** + * Diff a file. + * + * @param string $url + * @param int $revision + * @access public + * @return void + */ + public function diff($url, $revision) + { + $url = helper::safe64Decode($url); + $this->view->url = $url; + $this->view->revision = $revision; + $this->view->diff = $this->svn->diff($url, $revision); + + $this->display(); + } + + /** + * Cat a file. + * + * @param string $url + * @param int $revision + * @access public + * @return void + */ + public function cat($url, $revision) + { + $url = helper::safe64Decode($url); + $this->view->url = $url; + $this->view->revision = $revision; + $this->view->code = $this->svn->cat($url, $revision); + + $this->display(); + } + + /** + * Sync from the syncer by api. + * + * @access public + * @return void + */ + public function apiSync() + { + if($this->post->logs) + { + $repoRoot = $this->post->repoRoot; + $logs = stripslashes($this->post->logs); + $logs = simplexml_load_string($logs); + foreach($logs->logentry as $entry) + { + $parsedLogs[] = $this->svn->convertLog($entry); + } + $parsedObjects = array('stories' => array(), 'tasks' => array(), 'bugs' => array()); + foreach($parsedLogs as $log) + { + $objects = $this->svn->parseComment($log->msg); + if($objects) + { + $this->svn->saveAction2PMS($objects, $log, $repoRoot); + if($objects['stories']) $parsedObjects['stories'] = array_merge($parsedObjects['stories'], $objects['stories']); + if($objects['tasks']) $parsedObjects['tasks' ] = array_merge($parsedObjects['tasks'], $objects['tasks']); + if($objects['bugs']) $parsedObjects['bugs'] = array_merge($parsedObjects['bugs'], $objects['bugs']); + } + } + $parsedObjects['stories'] = array_unique($parsedObjects['stories']); + $parsedObjects['tasks'] = array_unique($parsedObjects['tasks']); + $parsedObjects['bugs'] = array_unique($parsedObjects['bugs']); + $this->view->parsedObjects = $parsedObjects; + $this->display(); + exit; + } + } +} diff --git a/trunk/module/svn/lang/en.php b/trunk/module/svn/lang/en.php new file mode 100644 index 0000000000..ff31b6a2fa --- /dev/null +++ b/trunk/module/svn/lang/en.php @@ -0,0 +1,16 @@ + + * @package svn + * @version $Id$ + * @link http://www.zentao.net + */ +/* Methods. */ +$lang->svn->common = 'Subversion'; +$lang->svn->cat = 'View code'; +$lang->svn->diff = 'Diff code'; +$lang->svn->apiSync = 'API: sync svn log'; diff --git a/trunk/module/svn/lang/zh-cn.php b/trunk/module/svn/lang/zh-cn.php new file mode 100644 index 0000000000..09f1573c15 --- /dev/null +++ b/trunk/module/svn/lang/zh-cn.php @@ -0,0 +1,16 @@ + + * @package svn + * @version $Id$ + * @link http://www.zentao.net + */ +/* 方法列表。*/ +$lang->svn->common = 'Subversion'; +$lang->svn->cat = '查看源代码'; +$lang->svn->diff = '比较源代码'; +$lang->svn->apiSync = '接口:同步svn日志'; diff --git a/trunk/module/svn/lang/zh-tw.php b/trunk/module/svn/lang/zh-tw.php new file mode 100644 index 0000000000..d7f922aff3 --- /dev/null +++ b/trunk/module/svn/lang/zh-tw.php @@ -0,0 +1,16 @@ + + * @package svn + * @version $Id$ + * @link http://www.zentao.net + */ +/* 方法列表。*/ +$lang->svn->common = 'Subversion'; +$lang->svn->cat = '查看原始碼'; +$lang->svn->diff = '比較原始碼'; +$lang->svn->apiSync = '介面:同步svn日誌'; diff --git a/trunk/module/svn/model.php b/trunk/module/svn/model.php new file mode 100644 index 0000000000..30f3e35b64 --- /dev/null +++ b/trunk/module/svn/model.php @@ -0,0 +1,653 @@ + + * @package svn + * @version $Id$ + * @link http://www.zentao.net + */ +?> +loadModel('action'); + } + + /** + * Run. + * + * @access public + * @return void + */ + public function run() + { + $this->setRepos(); + $this->setLogRoot(); + $this->setRestartFile(); + + foreach($this->repos as $name => $repo) + { + $this->printLog("begin repo $name"); + $repo = (object)$repo; + $repo->name = $name; + $this->setRepo($repo); + + $savedRevision = $this->getSavedRevision(); + $this->printLog("start from revision $savedRevision"); + $logs = $this->getRepoLogs($repo, $savedRevision); + $this->printLog("get " . count($logs) . " logs"); + + $this->printLog('begin parsing logs'); + foreach($logs as $log) + { + $this->printLog("parsing log {$log->revision}"); + if($log->revision == $savedRevision) + { + $this->printLog("{$log->revision} alread parsed, ommit it"); + continue; + } + + $this->printLog("comment is\n----------\n" . trim($log->msg) . "\n----------"); + $objects = $this->parseComment($log->msg); + if($objects) + { + $this->printLog('extract' . + 'story:' . join(' ', $objects['stories']) . + ' task:' . join(' ', $objects['tasks']) . + ' bug:' . join(',', $objects['bugs'])); + + $this->saveAction2PMS($objects, $log); + } + else + { + $this->printLog('no objects found' . "\n"); + } + if($log->revision > $savedRevision) $savedRevision = $log->revision; + } + $this->saveLastRevision($savedRevision); + $this->printLog("save revision $savedRevision"); + $this->deleteRestartFile(); + $this->printLog("\n\nrepo $name finished"); + } + } + + /** + * Set the log root. + * + * @access public + * @return void + */ + public function setLogRoot() + { + $this->logRoot = $this->app->getTmpRoot() . 'svn/'; + if(!is_dir($this->logRoot)) mkdir($this->logRoot); + } + + /** + * Set the restart file. + * + * @access public + * @return void + */ + public function setRestartFile() + { + $this->restartFile = dirname(__FILE__) . '/restart'; + } + + /** + * Delete the restart file. + * + * @access public + * @return void + */ + public function deleteRestartFile() + { + if(is_file($this->restartFile)) unlink($this->restartFile); + } + + /** + * Set the repos. + * + * @access public + * @return void + */ + public function setRepos() + { + if(!$this->config->svn->repos) die("You must set one svn repo.\n"); + $this->repos = $this->config->svn->repos; + } + + /** + * Set repo. + * + * @param object $repo + * @access public + * @return void + */ + public function setRepo($repo) + { + $this->setClient($repo); + $this->setLogFile($repo->name); + $this->setRepoRoot($repo); + } + + /** + * Set the svn binary client of a repo. + * + * @param object $repo + * @access public + * @return void + */ + public function setClient($repo) + { + if($this->config->svn->client == '') die("You must set the svn client file.\n"); + $this->client = $this->config->svn->client . " --non-interactive"; + if(stripos($repo->path, 'https') === 0 or stripos($repo->path, 'svn') === 0) + { + $cmd = $this->config->svn->client . ' --version --quiet'; + $version = `$cmd`; + if(version_compare($version, '1.6.0', '>')) + { + $this->client .= ' --trust-server-cert'; + } + } + if(isset($repo->username)) $this->client .= " --username $repo->username --password $repo->password --no-auth-cache"; + } + + /** + * Set the log file of a repo. + * + * @param string $repoName + * @access public + * @return void + */ + public function setLogFile($repoName) + { + $this->logFile = $this->logRoot . $repoName; + } + + /** + * set the root path of a repo. + * + * @param object $repo + * @access public + * @return void + */ + public function setRepoRoot($repo) + { + $cmd = $this->client . " info --xml $repo->path"; + $info = `$cmd`; + $info = simplexml_load_string($info); + $repoRoot = $info->entry->repository->root; + $this->repoRoot = $repoRoot; + } + + /** + * Get repo logs. + * + * @param object $repo + * @param int $fromRevision + * @access public + * @return array + */ + public function getRepoLogs($repo, $fromRevision) + { + $parsedLogs = array(); + + /* The svn log command. */ + $cmd = $this->client . " log -r $fromRevision:HEAD -v --xml $repo->path"; + $rawLogs = `$cmd`; + $logs = @simplexml_load_string($rawLogs); // Convert it to object. + if(!$logs) die("Some error occers: \nThe command is $cmd\n the svn logs is $rawLogs\n"); + + /* Process logs. */ + foreach($logs->logentry as $entry) $parsedLogs[] = $this->convertLog($entry); + return $parsedLogs; + } + + /** + * Convert log from xml format to object. + * + * @param object $log + * @access public + * @return ojbect + */ + public function convertLog($log) + { + /* Get author, revision, msg, date attributes. */ + $parsedLog = new stdClass(); + $parsedLog->author = (string)$log->author; + $parsedLog->revision = (int)$log['revision']; + $parsedLog->msg = trim((string)$log->msg); + $parsedLog->date = date('Y-m-d H:i:s', strtotime($log->date)); + + /* Process files. */ + $parsedLog->files = array(); + foreach ($log->paths as $key => $paths) + { + $parsedFiles = array(); + foreach($paths as $path) + { + $action = (string)$path['action']; + $parsedFiles[$action][] = (string)$path; + } + } + $parsedLog->files = $parsedFiles; + + return $parsedLog; + } + + /** + * Parse the comment of svn, extract object id list from it. + * + * @param string $comment + * @access public + * @return array + */ + public function parseComment($comment) + { + $stories = array(); + $tasks = array(); + $bugs = array(); + + // bug|story|task(case insensitive) + some space + #|:|:(Chinese) + id lists(maybe join with space or ,) + // $comment = "bug # 1,2,3,4 Bug:1 2 3 4 5 story:9999,1234566 story:456,1234566"; + $commonReg = "(?:\s){0,}(?:#|:|:){0,}([0-9, ]{1,})"; + $taskReg = '/task' . $commonReg . '/i'; + $storyReg = '/story' . $commonReg . '/i'; + $bugReg = '/bug' . $commonReg . '/i'; + + if(preg_match_all($storyReg, $comment, $result)) $stories = join(' ', $result[1]); + if(preg_match_all($taskReg, $comment, $result)) $tasks = join(' ', $result[1]); + if(preg_match_all($bugReg, $comment, $result)) $bugs = join(' ', $result[1]); + + if($stories) $stories = array_unique(explode(' ', str_replace(',', ' ', $stories))); + if($tasks) $tasks = array_unique(explode(' ', str_replace(',', ' ', $tasks))); + if($bugs) $bugs = array_unique(explode(' ', str_replace(',', ' ', $bugs))); + + if(!$stories and !$tasks and !$bugs) return array(); + return array('stories' => $stories, 'tasks' => $tasks, 'bugs' => $bugs); + } + + /** + * Convert the comment to uft-8. + * + * @param string $comment + * @access public + * @return string + */ + public function iconvComment($comment) + { + /* Get encodings. */ + $encodings = str_replace(' ', '', trim($comment)); + if($encodings == '') return $comment; + $encodings = explode(',', $encodings); + + /* Try convert. */ + foreach($encodings as $encoding) + { + $result = @iconv($encoding, 'utf-8', $comment); + if($result) return $result; + } + + return $comment; + } + + /** + * Diff a url. + * + * @param string $url + * @param int $revision + * @access public + * @return string|bool + */ + public function diff($url, $revision) + { + $repo = $this->getRepoByURL($url); + if(!$repo) return false; + + $this->setClient($repo); + putenv('LC_CTYPE=en_US.UTF-8'); + + $oldRevision = $revision - 1; + + $url = str_replace('%2F', '/', urlencode($url)); + $url = str_replace('%3A', ':', $url); + + $cmd = $this->client . " diff -r $oldRevision:$revision $url"; + $diff = `$cmd`; + return $diff; + } + + /** + * Cat a url. + * + * @param string $url + * @param int $revision + * @access public + * @return string|bool + */ + public function cat($url, $revision) + { + $repo = $this->getRepoByURL($url); + if(!$repo) return false; + + $this->setClient($repo); + + putenv('LC_CTYPE=en_US.UTF-8'); + + $url = str_replace('%2F', '/', urlencode($url)); + $url = str_replace('%3A', ':', $url); + + $cmd = $this->client . " cat $url@$revision"; + $code = `$cmd`; + return $code; + } + + /** + * Get repo by url. + * + * @param string $url + * @access public + * @return object|bool + */ + public function getRepoByURL($url) + { + foreach($this->config->svn->repos as $repo) if(strpos($url, $repo['path']) !== false) return (object)$repo; + return false; + } + + /** + * Save action to pms. + * + * @param array $objects + * @param object $log + * @param string $repoRoot + * @access public + * @return void + */ + public function saveAction2PMS($objects, $log, $repoRoot = '') + { + $action->actor = $log->author; + $action->action = 'svncommited'; + $action->date = $log->date; + $action->comment = $this->iconvComment($log->msg); + $action->extra = $log->revision; + + $changes = $this->createActionChanges($log, $repoRoot); + + if($objects['stories']) + { + $products = $this->getStoryProducts($objects['stories']); + foreach($objects['stories'] as $storyID) + { + $storyID = (int)$storyID; + if(!isset($products[$storyID])) continue; + + $action->objectType = 'story'; + $action->objectID = $storyID; + $action->product = $products[$storyID]; + $action->project = 0; + + $this->saveRecord($action, $changes); + } + } + + if($objects['tasks']) + { + $productsAndProjects = $this->getTaskProductsAndProjects($objects['tasks']); + foreach($objects['tasks'] as $taskID) + { + $taskID = (int)$taskID; + if(!isset($productsAndProjects[$taskID])) continue; + + $action->objectType = 'task'; + $action->objectID = $taskID; + $action->product = $productsAndProjects[$taskID]['product']; + $action->project = $productsAndProjects[$taskID]['project']; + + $this->saveRecord($action, $changes); + } + } + + if($objects['bugs']) + { + $productsAndProjects = $this->getBugProductsAndProjects($objects['bugs']); + + foreach($objects['bugs'] as $bugID) + { + $bugID = (int)$bugID; + if(!isset($productsAndProjects[$bugID])) continue; + + $action->objectType = 'bug'; + $action->objectID = $bugID; + $action->product = $productsAndProjects[$bugID]->product; + $action->project = $productsAndProjects[$bugID]->project; + + $this->saveRecord($action, $changes); + } + } + } + + /** + * Save an action to pms. + * + * @param object $action + * @param object $log + * @access public + * @return bool + */ + public function saveRecord($action, $changes) + { + $record = $this->dao->select('*')->from(TABLE_ACTION) + ->where('objectType')->eq($action->objectType) + ->andWhere('objectID')->eq($action->objectID) + ->andWhere('extra')->eq($action->extra) + ->andWhere('action')->eq('svncommited') + ->fetch(); + if($record) + { + $this->dao->update(TABLE_ACTION)->data($action)->where('id')->eq($record->id)->exec(); + if($changes) + { + $historyID = $this->dao->findByAction($record->id)->from(TABLE_HISTORY)->fetch('id'); + $this->dao->update(TABLE_HISTORY)->data($changes)->where('id')->eq($historyID)->exec(); + } + } + else + { + $this->dao->insert(TABLE_ACTION)->data($action)->autoCheck()->exec(); + if($changes) + { + $actionID = $this->dao->lastInsertID(); + $this->action->logHistory($actionID, array($changes)); + } + } + } + + /** + * Create changes for action from a log. + * + * @param object $log + * @param string $repoRoot + * @access public + * @return array + */ + public function createActionChanges($log, $repoRoot) + { + if(!$log->files) return array(); + $diff = ''; + + $oldSelf = $this->server->PHP_SELF; + $this->server->set('PHP_SELF', $this->config->webRoot); + + if(!$repoRoot) $repoRoot = $this->repoRoot; + + foreach($log->files as $action => $actionFiles) + { + foreach($actionFiles as $file) + { + $param = array('url' => helper::safe64Encode($repoRoot . $file), 'revision' => $log->revision); + $catLink = trim(html::a(helper::createLink('svn', 'cat', $param, 'html'), 'view', '', "class='svnlink'")); + $diffLink = trim(html::a(helper::createLink('svn', 'diff', $param, 'html'), 'diff', '', "class='svnlink'")); + $diff .= $action . " " . $file . " $catLink "; + $diff .= $action == 'M' ? "$diffLink\n" : "\n" ; + } + } + $changes->field = 'subversion'; + $changes->old = ''; + $changes->new = ''; + $changes->diff = trim($diff); + + $this->server->set('PHP_SELF', $oldSelf); + return (array)$changes; + } + + /** + * Get products of stories. + * + * @param array $stories + * @access public + * @return array + */ + public function getStoryProducts($stories) + { + return $this->dao->select('id, product')->from(TABLE_STORY)->where('id')->in($stories)->fetchPairs(); + } + + /** + * Get products and projects of tasks. + * + * @param array $tasks + * @access public + * @return array + */ + public function getTaskProductsAndProjects($tasks) + { + $records = array(); + $products = $this->dao->select('t1.id, t2.product') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->where('t1.id')->in($tasks)->fetchPairs(); + + $projects = $this->dao->select('id, project')->from(TABLE_TASK)->where('id')->in($tasks)->fetchPairs(); + + foreach($projects as $taskID => $projectID) + { + $record = array(); + $record['project'] = $projectID; + $record['product'] = isset($products[$taskID]) ? $products[$taskID] : 0; + $records[$taskID] = $record; + } + return $records; + } + + /** + * Get products and projects of bugs. + * + * @param array $bugs + * @access public + * @return array + */ + public function getBugProductsAndProjects($bugs) + { + return $this->dao->select('id, project, product')->from(TABLE_BUG)->where('id')->in($bugs)->fetchAll('id'); + } + + /** + * Get the saved revision. + * + * @access public + * @return int + */ + public function getSavedRevision() + { + if(!file_exists($this->logFile)) return 0; + if(file_exists($this->restartFile)) return 0; + return (int)trim(file_get_contents($this->logFile)); + } + + /** + * Save the last revision. + * + * @param int $revision + * @access public + * @return void + */ + public function saveLastRevision($revision) + { + file_put_contents($this->logFile, $revision); + } + + /** + * Pring log. + * + * @param sting $log + * @access public + * @return void + */ + public function printLog($log) + { + echo helper::now() . " $log\n"; + } +} diff --git a/trunk/module/svn/syncer/Makefile b/trunk/module/svn/syncer/Makefile new file mode 100644 index 0000000000..cdfd0afeb9 --- /dev/null +++ b/trunk/module/svn/syncer/Makefile @@ -0,0 +1,14 @@ +VERSION=$(shell head -n 1 VERSION) + +all: zip + +clean: + rm -fr syncer + rm -fr *.zip +zip: + mkdir -p syncer/ + cp -fr ../../../lib/api/api.class.php syncer/ + cp -fr config.php syncer/ + cp -fr syncer.php syncer/ + zip -r -9 svnsyncer.$(VERSION).zip syncer + rm -fr syncer diff --git a/trunk/module/svn/syncer/VERSION b/trunk/module/svn/syncer/VERSION new file mode 100644 index 0000000000..261c8fe0a2 --- /dev/null +++ b/trunk/module/svn/syncer/VERSION @@ -0,0 +1 @@ +1.0.beta diff --git a/trunk/module/svn/syncer/config.php b/trunk/module/svn/syncer/config.php new file mode 100644 index 0000000000..bed9740d90 --- /dev/null +++ b/trunk/module/svn/syncer/config.php @@ -0,0 +1,44 @@ +svn->client = '/usr/bin/svn'; // c:\svn\svn.exe + * $config->svn->repos['pms']['path'] = 'http://svn.zentao.net/zentao/trunk/'; + * $config->svn->repos['pms']['username'] = 'user'; + * $config->svn->repos['pms']['password'] = 'pass'; + * + * $config->zentao->path = "http://pms.zentao.net/"; + * $config->zentao->user = 'demo'; + * $config->zentao->password = '123456'; + * + */ +/* System settings. */ +$config->timezone = 'Asia/Shanghai'; // The time zone setting, for more see http://www.php.net/manual/en/timezones.php +$config->sleep = 0; // The seconds to sleep. + +/* Subversion settings. */ +$config->svn->client = ''; + +$i = 1; +$config->svn->repos[$i]['path'] = ''; +$config->svn->repos[$i]['username'] = ''; +$config->svn->repos[$i]['password'] = ''; + +/* +$i ++; +$config->svn->repos[$i]['path'] = ''; +$config->svn->repos[$i]['username'] = ''; +$config->svn->repos[$i]['password'] = ''; +*/ + +/* ZenTaoPMS settings. */ +$config->zentao->path = ''; +$config->zentao->user = ''; +$config->zentao->password = ''; diff --git a/trunk/module/svn/syncer/syncer.php b/trunk/module/svn/syncer/syncer.php new file mode 100644 index 0000000000..4285162c3b --- /dev/null +++ b/trunk/module/svn/syncer/syncer.php @@ -0,0 +1,299 @@ + + * @package svn + * @version $Id$ + * @link http://www.zentao.net + */ +error_reporting(E_ALL ^ E_STRICT ^ E_WARNING); + +include './config.php'; +include './api.class.php'; + +$syncer = new syncer($config); +$syncer->run(); + +class syncer +{ + /** + * The svn binary svnClient. + * + * @var string + * @access public + */ + public $svnClient; + + /** + * The zentao client. + * + * @var string + * @access public + */ + public $zentaoClient; + + /** + * Repos. + * + * @var array + * @access public + */ + public $repos = array(); + + /** + * The log root. + * + * @var string + * @access public + */ + public $logRoot = ''; + + /** + * The construct function. + * + * @access public + * @return void + */ + public function __construct($config) + { + $this->setConfig($config); + $this->setTimeZone(); + $this->setRepos(); + $this->setLogRoot(); + $this->loginZentao(); + } + + /** + * Set config. + * + * @param object $config + * @access public + * @return void + */ + public function setConfig($config) + { + $this->config = $config; + } + + /** + * Set timezone. + * + * @access public + * @return void + */ + public function setTimeZone() + { + date_default_timezone_set($this->config->timezone); + } + + /** + * Set the repos. + * + * @access public + * @return void + */ + public function setRepos() + { + if(!$this->config->svn->repos) die("You must set one svn repo.\n"); + $this->repos = $this->config->svn->repos; + } + + /** + * Set the log root. + * + * @access public + * @return void + */ + public function setLogRoot() + { + $this->logRoot = './log/'; + if(!is_dir($this->logRoot)) mkdir($this->logRoot); + } + + /** + * Login to zentao. + * + * @access public + * @return void + */ + public function loginZentao() + { + if(!$this->config->zentao->path or !$this->config->zentao->user) die("You must set the zentao path and user.\n"); + $zentaoConfig = $this->config->zentao; + $this->zentaoClient = new ztclient($zentaoConfig->path, $zentaoConfig->user, $zentaoConfig->password); + } + + /** + * Run. + * + * @access public + * @return void + */ + public function run() + { + while(true) + { + foreach($this->repos as $name => $repo) + { + $this->printLog("begin repo $name"); + $repo = (object)$repo; + $repo->name = $name; + + $this->setRepo($repo); + + $savedRevision = $this->getSavedRevision(); + $this->printLog("start from revision $savedRevision"); + $logs = $this->getRepoLogs($repo, $savedRevision); + $revisions = $this->getRevisionsFromLogs($logs); + if(!$revisions) + { + $this->printLog("no logs"); + continue; + } + $this->printLog('fetched ' . count($revisions) . ' logs'); + + $this->printLog('begin posting logs'); + $objects = $this->zentaoClient->post('svn', 'apiSync', array('logs' => $logs, 'repoRoot' => $this->repoRoot)); + $objects = $objects->parsedObjects; + + $this->printLog('parsed objects:'); + echo 'story: ' . join(',', (array)$objects->stories) . "\n"; + echo 'task: ' . join(',', (array)$objects->tasks) . "\n"; + echo 'bugs: ' . join(',', (array)$objects->bugs) . "\n"; + + $this->saveLastRevision(max($revisions)); + echo "----------------------\n"; + } + $this->printLog("sleeping {$this->config->sleep} seconds"); + sleep($this->config->sleep); + } + } + + /** + * Set repo. + * + * @param object $repo + * @access public + * @return void + */ + public function setRepo($repo) + { + $this->setClient($repo); + $this->setLogFile($repo->name); + $this->setRepoRoot($repo); + } + + /** + * Set the svn binary svnClient of a repo. + * + * @param object $repo + * @access public + * @return void + */ + public function setClient($repo) + { + if($this->config->svn->client == '') die("You must set the svn svnClient file.\n"); + $this->svnClient = $this->config->svn->client . " --non-interactive"; + if(isset($repo->username)) $this->svnClient .= " --username $repo->username --password $repo->password --no-auth-cache"; + } + + /** + * Set the log file of a repo. + * + * @param string $repoName + * @access public + * @return void + */ + public function setLogFile($repoName) + { + $this->logFile = $this->logRoot . $repoName; + } + + /** + * set the root path of a repo. + * + * @param object $repo + * @access public + * @return void + */ + public function setRepoRoot($repo) + { + $cmd = $this->svnClient . " info --xml $repo->path"; + $info = `$cmd`; + $info = simplexml_load_string($info); + $repoRoot = (string)$info->entry->repository->root; + $this->repoRoot = $repoRoot; + } + + /** + * Get repo logs. + * + * @param object $repo + * @param int $fromRevision + * @access public + * @return string + */ + public function getRepoLogs($repo, $fromRevision) + { + $parsedLogs = array(); + + /* The svn log command. */ + $cmd = $this->svnClient . " log -r $fromRevision:HEAD -v --xml $repo->path"; + $logs = `$cmd`; + + return $logs; + } + + /** + * Get the saved revision. + * + * @access public + * @return int + */ + public function getSavedRevision() + { + if(!file_exists($this->logFile)) return 0; + return (int)trim(file_get_contents($this->logFile)); + } + + /** + * Get revisons from logs. + * + * @param string $logs + * @access public + * @return array|bool + */ + public function getRevisionsFromLogs($logs) + { + if(!preg_match_all('|revision="(.*)"|', $logs, $results)) return false; + $revisions = $results[1]; + return $revisions; + } + + /** + * Save the last revision. + * + * @param int $revision + * @access public + * @return void + */ + public function saveLastRevision($revision) + { + file_put_contents($this->logFile, $revision); + } + + /** + * Pring log. + * + * @param sting $log + * @access public + * @return void + */ + public function printLog($log) + { + echo date('Y-m-d H:i:s') . " $log\n"; + } +} diff --git a/trunk/module/svn/view/cat.html.php b/trunk/module/svn/view/cat.html.php new file mode 100644 index 0000000000..e3e158227c --- /dev/null +++ b/trunk/module/svn/view/cat.html.php @@ -0,0 +1,16 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +
      <?php echo $code;?>
      + diff --git a/trunk/module/svn/view/diff.html.php b/trunk/module/svn/view/diff.html.php new file mode 100644 index 0000000000..6fc5ab0d9e --- /dev/null +++ b/trunk/module/svn/view/diff.html.php @@ -0,0 +1,17 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      +
      <?php echo $diff;?>
      + diff --git a/trunk/module/task/config.php b/trunk/module/task/config.php new file mode 100644 index 0000000000..31daac0ea7 --- /dev/null +++ b/trunk/module/task/config.php @@ -0,0 +1,21 @@ +task->batchCreate = 10; +$config->task->create->requiredFields = 'name,type'; +$config->task->edit->requiredFields = $config->task->create->requiredFields; +$config->task->start->requiredFields = 'estimate'; +$config->task->finish->requiredFields = 'consumed'; +$config->task->activate->requiredFields = 'left'; + +$config->task->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->task->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); + +$config->task->exportFields = ' + id, project, story, + name, desc, + type, pri, deadline, status,estimate, consumed, left, + mailto, + openedBy, openedDate, assignedTo, assignedDate, + finishedBy, finishedDate, canceledBy, canceledDate, + closedBy, closedDate, closedReason, + lastEditedBy, lastEditedDate,files + '; diff --git a/trunk/module/task/control.php b/trunk/module/task/control.php new file mode 100644 index 0000000000..9389c3cc73 --- /dev/null +++ b/trunk/module/task/control.php @@ -0,0 +1,829 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +class task extends control +{ + /** + * Construct function, load model of project and story modules. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('project'); + $this->loadModel('story'); + $this->loadModel('tree'); + } + + /** + * Create a task. + * + * @param int $projectID + * @param int $storyID + * @access public + * @return void + */ + public function create($projectID = 0, $storyID = 0, $moduleID = 0) + { + $project = $this->project->getById($projectID); + $taskLink = $this->createLink('project', 'browse', "projectID=$projectID&tab=task"); + $storyLink = $this->session->storyList ? $this->session->storyList : $this->createLink('project', 'story', "projectID=$projectID"); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + + /* Set menu. */ + $this->project->setMenu($this->project->getPairs(), $project->id); + + if(!empty($_POST)) + { + $tasksID = $this->task->create($projectID); + if(dao::isError()) die(js::error(dao::getError())); + + /* Create actions. */ + $this->loadModel('action'); + foreach($tasksID as $taskID) + { + $actionID = $this->action->create('task', $taskID, 'Opened', ''); + $this->sendmail($taskID, $actionID); + } + + /* Locate the browser. */ + if($this->post->after == 'continueAdding') + { + echo js::alert($this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding']); + die(js::locate($this->createLink('task', 'create', "projectID=$projectID&storyID={$this->post->story}"), 'parent')); + } + elseif($this->post->after == 'toTastList') + { + die(js::locate($taskLink, 'parent')); + } + elseif($this->post->after == 'toStoryList') + { + die(js::locate($storyLink, 'parent')); + } + } + + $stories = $this->story->getProjectStoryPairs($projectID); + $members = $this->project->getTeamMemberPairs($projectID, 'nodeleted'); + $moduleOptionMenu = $this->tree->getOptionMenu($projectID, $viewType = 'task'); + $header['title'] = $project->name . $this->lang->colon . $this->lang->task->create; + $position[] = html::a($taskLink, $project->name); + $position[] = $this->lang->task->create; + $this->view->header = $header; + $this->view->position = $position; + $this->view->project = $project; + $this->view->stories = $stories; + $this->view->storyID = $storyID; + $this->view->members = $members; + $this->view->moduleID = $moduleID; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->display(); + } + + /** + * Batch create task. + * + * @param int $projectID + * @param int $storyID + * @access public + * @return void + */ + public function batchCreate($projectID = 0, $storyID = 0) + { + $project = $this->project->getById($projectID); + $taskLink = $this->createLink('project', 'browse', "projectID=$projectID&tab=task"); + $storyLink = $this->session->storyList ? $this->session->storyList : $this->createLink('project', 'story', "projectID=$projectID"); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + + /* Set menu. */ + $this->project->setMenu($this->project->getPairs(), $project->id); + + if(!empty($_POST)) + { + $mails = $this->task->batchCreate($projectID); + if(dao::isError()) die(js::error(dao::getError())); + + foreach($mails as $mail) + { + $this->sendmail($mail->taskID, $mail->actionID); + } + + /* Locate the browser. */ + die(js::locate($taskLink, 'parent')); + } + + $stories = $this->story->getProjectStoryPairs($projectID); + $stories['ditto'] = $this->lang->task->ditto; + $members = $this->project->getTeamMemberPairs($projectID, 'nodeleted'); + $header['title'] = $project->name . $this->lang->colon . $this->lang->task->create; + $position[] = html::a($taskLink, $project->name); + $position[] = $this->lang->task->create; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->project = $project; + $this->view->stories = $stories; + $this->view->storyID = $storyID; + $this->view->members = $members; + $this->display(); + } + + /** + * Common actions of task module. + * + * @param int $taskID + * @access public + * @return void + */ + public function commonAction($taskID) + { + $this->view->task = $this->loadModel('task')->getByID($taskID); + $this->view->project = $this->project->getById($this->view->task->project); + $this->view->members = $this->project->getTeamMemberPairs($this->view->project->id ,'nodeleted'); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->actions = $this->loadModel('action')->getList('task', $taskID); + + /* Set menu. */ + $this->project->setMenu($this->project->getPairs(), $this->view->project->id); + $this->view->position[] = html::a($this->createLink('project', 'browse', "project={$this->view->task->project}"), $this->view->project->name); + + } + + /** + * Edit a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function edit($taskID, $comment = false) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = array(); + $files = array(); + if($comment == false) + { + $changes = $this->task->update($taskID); + if(dao::isError()) die(js::error(dao::getError())); + $files = $this->loadModel('file')->saveUpload('task', $taskID); + } + + $task = $this->task->getById($taskID); + if($this->post->comment != '' or !empty($changes) or !empty($files)) + { + $action = !empty($changes) ? 'Edited' : 'Commented'; + $fileAction = ''; + if(!empty($files)) $fileAction = $this->lang->addFiles . join(',', $files) . "\n" ; + $actionID = $this->action->create('task', $taskID, $action, $fileAction . $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + + if($task->fromBug != 0) + { + foreach($changes as $change) + { + if($change['field'] == 'status') + { + $confirmURL = $this->createLink('bug', 'view', "id=$task->fromBug"); + $cancelURL = $this->server->HTTP_REFERER; + die(js::confirm(sprintf($this->lang->task->remindBug, $task->fromBug), $confirmURL, $cancelURL, 'parent', 'parent')); + } + } + } + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->lang->task->edit; + $this->view->position[] = $this->lang->task->edit; + $this->view->stories = $this->story->getProjectStoryPairs($this->view->project->id); + $this->view->members = $this->loadModel('user')->appendDeleted($this->view->members, $this->view->task->assignedTo); + $this->view->modules = $this->tree->getOptionMenu($this->view->task->project, $viewType = 'task'); + $this->display(); + } + + /** + * Batch edit task. + * + * @param int $projectID + * @param string $from example:projectTask, taskBatchEdit + * @param string $orderBy + * @access public + * @return void + */ + public function batchEdit($projectID = 0, $from = '', $orderBy = '') + { + /* Get form data for project-task. */ + if($from == 'projectTask') + { + /* Initialize vars. */ + if(!$orderBy) $orderBy = $this->cookie->projectTaskOrder ? $this->cookie->projectTaskOrder : 'status,id_desc'; + $project = $this->project->getById($projectID); + $taskIDList = $this->post->taskIDList ? $this->post->taskIDList : array(); + $editedTasks = array(); + $columns = 13; + $showSuhosinInfo = false; + + /* Set project menu. */ + $this->project->setMenu($this->project->getPairs(), $project->id); + + /* Get all tasks. */ + $allTasks = $this->dao->select('*')->from(TABLE_TASK)->alias('t1')->where($this->session->taskQueryCondition)->orderBy($orderBy)->fetchAll('id'); + if(!$allTasks) $allTasks = array(); + + /* Initialize the tasks whose need to edited. */ + foreach($allTasks as $task) if(in_array($task->id, $taskIDList)) $editedTasks[$task->id] = $task; + + /* Judge whether the editedTasks is too large. */ + $showSuhosinInfo = $this->loadModel('common')->judgeSuhosinSetting(count($editedTasks), $columns); + + /* Set the sessions. */ + $this->app->session->set('showSuhosinInfo', $showSuhosinInfo); + + /* Assign. */ + $this->view->header['title'] = $project->name . $this->lang->colon . $this->lang->task->batchEdit; + $this->view->position[] = $this->lang->task->common; + $this->view->position[] = $this->lang->task->batchEdit; + + if($showSuhosinInfo) $this->view->suhosinInfo = $this->lang->suhosinInfo; + $this->view->projectID = $project->id; + $this->view->modules = $this->tree->getOptionMenu($projectID, $viewType = 'task'); + $this->view->editedTasks = $editedTasks; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->members = $this->project->getTeamMemberPairs($projectID, 'nodeleted'); + + $this->display(); + } + /* Get form data for task-batchEdit. */ + elseif($from == 'taskBatchEdit') + { + $allChanges = $this->task->batchUpdate(); + + if(!empty($allChanges)) + { + foreach($allChanges as $taskID => $changes) + { + if(!empty($changes)) + { + $actionID = $this->loadModel('action')->create('task', $taskID, 'Edited'); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + + $task = $this->task->getById($taskID); + if($task->fromBug != 0) + { + foreach($changes as $change) + { + if($change['field'] == 'status') + { + $confirmURL = $this->createLink('bug', 'view', "id=$task->fromBug"); + $cancelURL = $this->server->HTTP_REFERER; + die(js::confirm(sprintf($this->lang->task->remindBug, $task->fromBug), $confirmURL, $cancelURL, 'parent', 'parent')); + } + } + } + } + } + } + die(js::locate($this->session->taskList, 'parent')); + } + } + + /** + * Update assign of task + * + * @param int $requestID + * @access public + * @return void + */ + public function assignTo($projectID, $taskID) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->assign($taskID); + if(dao::isError()) die(js::error(dao::getError())); + $actionID = $this->action->create('task', $taskID, 'Assigned', $this->post->comment, $this->post->assignedTo); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->view->project->name . $this->lang->colon . $this->lang->task->assign; + $this->view->position[] = $this->lang->task->assign; + + $this->view->users = $this->project->getTeamMemberPairs($projectID); + $this->display(); + } + + /** + * View a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function view($taskID) + { + $task = $this->task->getById($taskID, true); + if(!$task) die(js::error($this->lang->notFound) . js::locate('back')); + + /* Set menu. */ + $project = $this->project->getById($task->project); + $this->project->setMenu($this->project->getPairs(), $project->id); + + $header['title'] = "TASK#$task->id $task->name / $project->name"; + $position[] = html::a($this->createLink('project', 'browse', "projectID=$task->project"), $project->name); + $position[] = $task->name; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->project = $project; + $this->view->task = $task; + $this->view->actions = $this->loadModel('action')->getList('task', $taskID); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('task', $taskID); + $this->view->modulePath = $this->tree->getParents($task->module); + $this->display(); + } + + /** + * Confirm story change + * + * @param int $taskID + * @access public + * @return void + */ + public function confirmStoryChange($taskID) + { + $task = $this->task->getById($taskID); + $this->dao->update(TABLE_TASK)->set('storyVersion')->eq($task->latestStoryVersion)->where('id')->eq($taskID)->exec(); + $this->loadModel('action')->create('task', $taskID, 'confirmed', '', $task->latestStoryVersion); + die(js::reload('parent')); + } + + /** + * Start a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function start($taskID) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->start($taskID); + if(dao::isError()) die(js::error(dao::getError())); + + if($this->post->comment != '' or !empty($changes)) + { + $actionID = $this->action->create('task', $taskID, 'Started', $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->view->project->name . $this->lang->colon .$this->lang->task->start; + $this->view->position[] = $this->lang->task->start; + $this->display(); + } + + /** + * Finish a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function finish($taskID) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->finish($taskID); + if(dao::isError()) die(js::error(dao::getError())); + + $task = $this->task->getById($taskID); + if($this->post->comment != '' or !empty($changes)) + { + $actionID = $this->action->create('task', $taskID, 'Finished', $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + + if($task->fromBug != 0) + { + foreach($changes as $change) + { + if($change['field'] == 'status') + { + $confirmURL = $this->createLink('bug', 'view', "id=$task->fromBug"); + $cancelURL = $this->server->HTTP_REFERER; + die(js::confirm(sprintf($this->lang->task->remindBug, $task->fromBug), $confirmURL, $cancelURL, 'parent', 'parent')); + } + } + } + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->view->project->name . $this->lang->colon .$this->lang->task->finish; + $this->view->position[] = $this->lang->task->finish; + $this->view->date = strftime("%Y-%m-%d %X", strtotime('now')); + + $this->display(); + } + + /** + * Close a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function close($taskID) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->close($taskID); + if(dao::isError()) die(js::error(dao::getError())); + + if($this->post->comment != '' or !empty($changes)) + { + $actionID = $this->action->create('task', $taskID, 'Closed', $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->view->project->name . $this->lang->colon .$this->lang->task->finish; + $this->view->position[] = $this->lang->task->finish; + + $this->display(); + + } + + /** + * Batch close tasks. + * + * @access public + * @return void + */ + public function batchClose() + { + if($this->post->tasks) + { + $tasks = $this->post->tasks; + unset($_POST['tasks']); + $this->loadModel('action'); + + foreach($tasks as $taskID) + { + $this->commonAction($taskID); + $task = $this->task->getById($taskID); + if($task->status == 'wait' or $task->status == 'doing') continue; + + $changes = $this->task->close($taskID); + + if($changes) + { + $actionID = $this->action->create('task', $taskID, 'Closed', ''); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + } + } + die(js::reload('parent')); + } + + /** + * Cancel a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function cancel($taskID) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->cancel($taskID); + if(dao::isError()) die(js::error(dao::getError())); + + if($this->post->comment != '' or !empty($changes)) + { + $actionID = $this->action->create('task', $taskID, 'Canceled', $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->view->project->name . $this->lang->colon .$this->lang->task->cancel; + $this->view->position[] = $this->lang->task->cancel; + + $this->display(); + } + + /** + * Activate a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function activate($taskID) + { + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->activate($taskID); + if(dao::isError()) die(js::error(dao::getError())); + + if($this->post->comment != '' or !empty($changes)) + { + $actionID = $this->action->create('task', $taskID, 'Activated', $this->post->comment); + $this->action->logHistory($actionID, $changes); + $this->sendmail($taskID, $actionID); + } + die(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->header->title = $this->view->project->name . $this->lang->colon .$this->lang->task->activate; + $this->view->position[] = $this->lang->task->activate; + $this->display(); + } + + /** + * Delete a task. + * + * @param int $projectID + * @param int $taskID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($projectID, $taskID, $confirm = 'no') + { + $task = $this->task->getById($taskID); + if($confirm == 'no') + { + die(js::confirm($this->lang->task->confirmDelete, inlink('delete', "projectID=$projectID&taskID=$taskID&confirm=yes"))); + } + else + { + $story = $this->dao->select('story')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch('story'); + $this->task->delete(TABLE_TASK, $taskID); + if($task->fromBug != 0) $this->dao->update(TABLE_BUG)->set('toTask')->eq(0)->where('id')->eq($task->fromBug)->exec(); + if($story) $this->loadModel('story')->setStage($story); + die(js::locate($this->session->taskList, 'parent')); + } + } + + /** + * Send email. + * + * @param int $taskID + * @param int $actionID + * @access public + * @return void + */ + public function sendmail($taskID, $actionID) + { + /* Set toList and ccList. */ + $task = $this->task->getById($taskID); + $projectName = $this->project->getById($task->project)->name; + $toList = $task->assignedTo; + $ccList = trim($task->mailto, ','); + + if($toList == '') + { + if($ccList == '') return; + if(strpos($ccList, ',') === false) + { + $toList = $ccList; + $ccList = ''; + } + else + { + $commaPos = strpos($ccList, ','); + $toList = substr($ccList, 0, $commaPos); + $ccList = substr($ccList, $commaPos + 1); + } + } + elseif(strtolower($toList) == 'closed') + { + $toList = $task->finishedBy; + } + + /* Get action info. */ + $action = $this->loadModel('action')->getById($actionID); + $history = $this->action->getHistory($actionID); + $action->history = isset($history[$actionID]) ? $history[$actionID] : array(); + + /* Create the email content. */ + $this->view->task = $task; + $this->view->action = $action; + $this->clear(); + $mailContent = $this->parse($this->moduleName, 'sendmail'); + + /* Send emails. */ + $this->loadModel('mail')->send($toList, $projectName . ':' . 'TASK#' . $task->id . $this->lang->colon . $task->name, $mailContent, $ccList); + if($this->mail->isError()) echo js::error($this->mail->getError()); + } + + /** + * AJAX: return tasks of a user in html select. + * + * @param string $account + * @param string $id + * @param string $status + * @access public + * @return string + */ + public function ajaxGetUserTasks($account = '', $id = '', $status = 'wait,doing') + { + if($account == '') $account = $this->app->user->account; + $tasks = $this->task->getUserTaskPairs($account, $status); + + if($id) die(html::select("tasks[$id]", $tasks, '', 'class="select-1 f-left"')); + die(html::select('task', $tasks, '', 'class=select-1')); + } + + /** + * AJAX: return project tasks in html select. + * + * @param int $projectID + * @param int $taskID + * @access public + * @return string + */ + public function ajaxGetProjectTasks($projectID, $taskID = 0) + { + $tasks = $this->task->getProjectTaskPairs((int)$projectID); + die(html::select('task', $tasks, $taskID)); + } + + /** + * The report page. + * + * @param int $projectID + * @param string $browseType + * @access public + * @return void + */ + public function report($projectID, $browseType = 'all') + { + + $this->loadModel('report'); + $this->view->charts = array(); + $this->view->renderJS = ''; + + if(!empty($_POST)) + { + foreach($this->post->charts as $chart) + { + $chartFunc = 'getDataOf' . $chart; + $chartData = $this->task->$chartFunc(); + $chartOption = $this->lang->task->report->$chart; + $this->task->mergeChartOption($chart); + + $chartXML = $this->report->createSingleXML($chartData, $chartOption->graph); + $this->view->charts[$chart] = $this->report->createJSChart($chartOption->swf, $chartXML, $chartOption->width, $chartOption->height); + $this->view->datas[$chart] = $this->report->computePercent($chartData); + } + $this->view->renderJS = $this->report->renderJsCharts(count($this->view->charts)); + } + + $this->project->setMenu($this->project->getPairs(), $projectID); + $this->projects = $this->project->getPairs(); + $this->view->header->title = $this->projects[$projectID] . $this->lang->colon . $this->lang->task->report->common; + $this->view->projectID = $projectID; + $this->view->browseType = $browseType; + $this->view->checkedCharts = $this->post->charts ? join(',', $this->post->charts) : ''; + + $this->display(); + } + + /** + * get data to export + * + * @param int $projectID + * @param string $orderBy + * @access public + * @return void + */ + public function export($projectID, $orderBy) + { + if($_POST) + { + $taskLang = $this->lang->task; + $taskConfig = $this->config->task; + + /* Create field lists. */ + $fields = explode(',', $taskConfig->exportFields); + foreach($fields as $key => $fieldName) + { + $fieldName = trim($fieldName); + $fields[$fieldName] = isset($taskLang->$fieldName) ? $taskLang->$fieldName : $fieldName; + unset($fields[$key]); + } + + /* Get tasks. */ + $tasks = $this->dao->select('*')->from(TABLE_TASK)->alias('t1')->where($this->session->taskQueryCondition)->orderBy($orderBy)->fetchAll('id'); + + /* Get users and projects. */ + $users = $this->loadModel('user')->getPairs('noletter'); + $projects = $this->loadModel('project')->getPairs('all'); + + /* Get related objects id lists. */ + $relatedStoryIdList = array(); + foreach($tasks as $task) $relatedStoryIdList[$task->story] = $task->story; + + /* Get related objects title or names. */ + $relatedStories = $this->dao->select('id,title')->from(TABLE_STORY) ->where('id')->in($relatedStoryIdList)->fetchPairs(); + $relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('task')->andWhere('objectID')->in(@array_keys($tasks))->fetchGroup('objectID'); + + foreach($tasks as $task) + { + if($this->post->fileType == 'csv') + { + $task->desc = htmlspecialchars_decode($task->desc); + $task->desc = str_replace("
      ", "\n", $task->desc); + $task->desc = str_replace('"', '""', $task->desc); + } + + /* fill some field with useful value. */ + $task->story = isset($relatedStories[$task->story]) ? $relatedStories[$task->story] : ''; + + if(isset($projects[$task->project])) $task->project = $projects[$task->project]; + if(isset($taskLang->typeList[$task->type])) $task->type = $taskLang->typeList[$task->type]; + if(isset($taskLang->priList[$task->pri])) $task->pri = $taskLang->priList[$task->pri]; + if(isset($taskLang->statusList[$task->status])) $task->status = $taskLang->statusList[$task->status]; + if(isset($taskLang->reasonList[$task->closedReason])) $task->closedReason = $taskLang->reasonList[$task->closedReason]; + + if(isset($users[$task->openedBy])) $task->openedBy = $users[$task->openedBy]; + if(isset($users[$task->assignedTo])) $task->assignedTo = $users[$task->assignedTo]; + if(isset($users[$task->finishedBy])) $task->finishedBy = $users[$task->finishedBy]; + if(isset($users[$task->canceledBy])) $task->canceledBy = $users[$task->canceledBy]; + if(isset($users[$task->closedBy])) $task->closedBy = $users[$task->closedBy]; + if(isset($users[$task->lastEditedBy])) $task->lastEditedBy = $users[$task->lastEditedBy]; + + $task->openedDate = substr($task->openedDate, 0, 10); + $task->assignedDate = substr($task->assignedDate, 0, 10); + $task->finishedDate = substr($task->finishedDate, 0, 10); + $task->canceledDate = substr($task->canceledDate, 0, 10); + $task->closedDate = substr($task->closedDate, 0, 10); + $task->lastEditedDate = substr($task->lastEditedDate, 0, 10); + + /* Set related files. */ + if(isset($relatedFiles[$task->id])) + { + foreach($relatedFiles[$task->id] as $file) + { + $fileURL = 'http://' . $this->server->http_host . $this->config->webRoot . "data/upload/$task->company/" . $file->pathname; + $task->files .= html::a($fileURL, $file->title, '_blank') . '
      '; + } + } + } + + $this->post->set('fields', $fields); + $this->post->set('rows', $tasks); + $this->post->set('kind', 'task'); + $this->fetch('file', 'export2' . $this->post->fileType, $_POST); + } + + $this->display(); + } +} diff --git a/trunk/module/task/css/create.css b/trunk/module/task/css/create.css new file mode 100644 index 0000000000..997d99b45e --- /dev/null +++ b/trunk/module/task/css/create.css @@ -0,0 +1,3 @@ +.button-c {padding:2px} + +caption .f-right a{color:red} diff --git a/trunk/module/task/css/edit.css b/trunk/module/task/css/edit.css new file mode 100644 index 0000000000..ccdb369518 --- /dev/null +++ b/trunk/module/task/css/edit.css @@ -0,0 +1,3 @@ +#story {width:220px} +.select-1 {width:220px} +.text-1 {width:215px} diff --git a/trunk/module/task/css/report.css b/trunk/module/task/css/report.css new file mode 100644 index 0000000000..9c3849153e --- /dev/null +++ b/trunk/module/task/css/report.css @@ -0,0 +1 @@ +.side span {display:block} diff --git a/trunk/module/task/js/batchcreate.js b/trunk/module/task/js/batchcreate.js new file mode 100755 index 0000000000..624f9d112d --- /dev/null +++ b/trunk/module/task/js/batchcreate.js @@ -0,0 +1,3 @@ +$(function() { + for(i=0; i + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->task->index = "Index"; +$lang->task->create = "Create"; +$lang->task->batchCreate = "Batch create"; +$lang->task->batchEdit = "Batch edit"; +$lang->task->import = "Import undone"; +$lang->task->edit = "Update"; +$lang->task->delete = "Delete"; +$lang->task->view = "Info"; +$lang->task->logEfforts = "Efforts"; +$lang->task->start = "Start"; +$lang->task->finish = "Finish"; +$lang->task->close = "Close"; +$lang->task->batchClose = "Batch close"; +$lang->task->cancel = "Cancel"; +$lang->task->activate = "Activate"; +$lang->task->export = "Export"; +$lang->task->reportChart = "Report chart"; +$lang->task->fromBug = 'From Bug'; +$lang->task->confirmStoryChange = "Confirm story change"; + +$lang->task->common = 'Task'; +$lang->task->id = 'ID'; +$lang->task->project = 'Project'; +$lang->task->module = 'Module'; +$lang->task->story = 'Story'; +$lang->task->storyVersion = 'Version'; +$lang->task->name = 'Name'; +$lang->task->type = 'Type'; +$lang->task->pri = 'Pri'; +$lang->task->mailto = 'Mailto'; +$lang->task->estimate = 'Estimate'; +$lang->task->estimateAB = 'Est'; +$lang->task->left = 'Left'; +$lang->task->leftAB = 'Left'; +$lang->task->consumed = 'Consumed'; +$lang->task->consumedAB = 'Use'; +$lang->task->hour = 'Hour'; +$lang->task->estStarted = 'Estimate start'; +$lang->task->realStarted = 'Real start'; +$lang->task->deadline = 'Deadline'; +$lang->task->deadlineAB = 'Deadline'; +$lang->task->status = 'Status'; +$lang->task->statusCustom = 'Status Order'; +$lang->task->desc = 'Desc'; +$lang->task->assign = 'Assign'; +$lang->task->assignTo = $lang->task->assign; +$lang->task->assignedTo = 'Assigned To'; +$lang->task->assignedToAB = 'Assign'; +$lang->task->assignedDate = 'Assigned Date'; +$lang->task->openedBy = 'Opened By'; +$lang->task->openedByAB = 'Open'; +$lang->task->openedDate = 'Opened Date'; +$lang->task->openedDateAB = 'Open'; +$lang->task->finishedBy = 'Finished By'; +$lang->task->finishedByAB = 'Finishe'; +$lang->task->finishedDate = 'Finished Date'; +$lang->task->finishedDateAB = 'Date'; +$lang->task->canceledBy = 'Canceled By'; +$lang->task->canceledDate = 'Canceled Date'; +$lang->task->closedBy = 'Closed By'; +$lang->task->closedDate = 'Closed Date'; +$lang->task->closedReason = 'Closed Reason'; +$lang->task->lastEditedBy = 'Last Edited By'; +$lang->task->lastEditedDate = 'Last Edited Date'; +$lang->task->lastEdited = 'Last Edited'; + +$lang->task->ditto = 'Ditto'; + +$lang->task->statusList[''] = ''; +$lang->task->statusList['wait'] = 'Pending'; +$lang->task->statusList['doing'] = 'In progress'; +$lang->task->statusList['done'] = 'Done'; +$lang->task->statusList['cancel'] = 'Canceled'; +$lang->task->statusList['closed'] = 'Closed'; + +$lang->task->typeList[''] = ''; +$lang->task->typeList['design'] = 'Design'; +$lang->task->typeList['devel'] = 'Devel'; +$lang->task->typeList['test'] = 'Test'; +$lang->task->typeList['study'] = 'Study'; +$lang->task->typeList['discuss'] = 'Discuss'; +$lang->task->typeList['ui'] = 'UI'; +$lang->task->typeList['affair'] = 'Affair'; +$lang->task->typeList['misc'] = 'Misc'; + +$lang->task->priList[0] = ''; +$lang->task->priList[3] = '3'; +$lang->task->priList[1] = '1'; +$lang->task->priList[2] = '2'; +$lang->task->priList[4] = '4'; + +$lang->task->reasonList[''] = ''; +$lang->task->reasonList['done'] = 'Done'; +$lang->task->reasonList['cancel'] = 'Canceled'; + +$lang->task->afterChoices['continueAdding'] = 'Continue to add task for this story. '; +$lang->task->afterChoices['toTastList'] = 'To task list. '; +$lang->task->afterChoices['toStoryList'] = 'To story list. '; + +$lang->task->buttonEdit = 'Edit'; +$lang->task->buttonClose = 'Close'; +$lang->task->buttonCancel = 'Cancel'; +$lang->task->buttonActivate = 'Activate'; +$lang->task->buttonLogEfforts = 'Efforts'; +$lang->task->buttonDelete = 'Delete'; +$lang->task->buttonBackToList = 'Back'; +$lang->task->buttonStart = 'Start'; +$lang->task->buttonDone = 'Done'; + +$lang->task->legendBasic = 'Basic info'; +$lang->task->legendEffort = 'Effort'; +$lang->task->legendLife = 'Lifetime'; +$lang->task->legendDesc = 'Desc'; +$lang->task->legendAction = 'Action'; + +$lang->task->ajaxGetUserTasks = "API:My tasks"; +$lang->task->ajaxGetProjectTasks = "API:Project tasks"; +$lang->task->confirmDelete = "Are you sure you want to delete this task?"; +$lang->task->copyStoryTitle = "Same as story"; +$lang->task->afterSubmit = "After created"; +$lang->task->successSaved = "Successfully saved"; +$lang->task->delayWarning = " Postponed %s days "; +$lang->task->remindBug = "This task from Bug, update the Bug:%s or not?"; + +/* Report. */ +$lang->task->report->common = 'Report'; +$lang->task->report->select = 'Select'; +$lang->task->report->create = 'Create'; + +$lang->task->report->charts['tasksPerProject'] = 'Project tasks'; +$lang->task->report->charts['tasksPerModule'] = 'Module tasks'; +$lang->task->report->charts['tasksPerAssignedTo'] = 'Assigned To'; +$lang->task->report->charts['tasksPerType'] = 'Type'; +$lang->task->report->charts['tasksPerPri'] = 'Priority'; +$lang->task->report->charts['tasksPerStatus'] = 'Status'; +$lang->task->report->charts['tasksPerDeadline'] = 'Deadline'; +$lang->task->report->charts['tasksPerEstimate'] = 'Estimate time'; +$lang->task->report->charts['tasksPerLeft'] = 'Left time'; +$lang->task->report->charts['tasksPerConsumed'] = 'Consumed time'; +$lang->task->report->charts['tasksPerFinishedBy'] = 'Finished By'; +$lang->task->report->charts['tasksPerClosedReason'] = 'Closed reason'; +$lang->task->report->charts['finishedTasksPerDay'] = 'Finished tasks per day'; + +$lang->task->report->options->swf = 'pie2d'; +$lang->task->report->options->width = 'auto'; +$lang->task->report->options->height = 300; +$lang->task->report->options->graph->baseFontSize = 12; +$lang->task->report->options->graph->showNames = 1; +$lang->task->report->options->graph->formatNumber = 1; +$lang->task->report->options->graph->decimalPrecision = 0; +$lang->task->report->options->graph->animation = 0; +$lang->task->report->options->graph->rotateNames = 0; +$lang->task->report->options->graph->yAxisName = 'COUNT'; +$lang->task->report->options->graph->pieRadius = 100; +$lang->task->report->options->graph->showColumnShadow = 0; + +$lang->task->report->tasksPerProject->graph->xAxisName = 'Project'; +$lang->task->report->tasksPerModule->graph->xAxisName = 'Module'; +$lang->task->report->tasksPerAssignedTo->graph->xAxisName = 'User'; +$lang->task->report->tasksPerType->graph->xAxisName = 'Type'; +$lang->task->report->tasksPerPri->graph->xAxisName = 'Pri'; +$lang->task->report->tasksPerStatus->graph->xAxisName = 'Status'; +$lang->task->report->tasksPerDeadline->graph->xAxisName = 'Date'; +$lang->task->report->tasksPerEstimate->graph->xAxisName = 'Time'; +$lang->task->report->tasksPerLeft->graph->xAxisName = 'Time'; +$lang->task->report->tasksPerConsumed->graph->xAxisName = 'Time'; +$lang->task->report->tasksPerFinishedBy->graph->xAxisName = 'User'; +$lang->task->report->tasksPerClosedReason->graph->xAxisName = 'Closed Reason'; + +$lang->task->report->finishedTasksPerDay->swf = 'column2d'; +$lang->task->report->finishedTasksPerDay->height = 400; +$lang->task->report->finishedTasksPerDay->graph->xAxisName = 'Date'; +$lang->task->report->finishedTasksPerDay->graph->rotateNames = '1'; + +$lang->task->placeholder->estimate = 'The estimated time for this task'; +$lang->task->placeholder->mailto = 'Mail to'; diff --git a/trunk/module/task/lang/zh-cn.php b/trunk/module/task/lang/zh-cn.php new file mode 100644 index 0000000000..8141660b67 --- /dev/null +++ b/trunk/module/task/lang/zh-cn.php @@ -0,0 +1,187 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->task->index = "任务一览"; +$lang->task->create = "新增"; +$lang->task->batchCreate = "批量添加"; +$lang->task->batchEdit = "批量编辑"; +$lang->task->import = "导入之前未完任务"; +$lang->task->edit = "编辑"; +$lang->task->delete = "删除"; +$lang->task->view = "查看任务"; +$lang->task->logEfforts = "记录工时"; +$lang->task->start = "开始"; +$lang->task->finish = "完成"; +$lang->task->close = "关闭"; +$lang->task->batchClose = "批量关闭"; +$lang->task->cancel = "取消"; +$lang->task->activate = "激活"; +$lang->task->export = "导出"; +$lang->task->reportChart = "报表统计"; +$lang->task->fromBug = '来源Bug'; +$lang->task->confirmStoryChange = "确认需求变动"; + +$lang->task->common = '任务'; +$lang->task->id = '编号'; +$lang->task->project = '所属项目'; +$lang->task->module = '所属模块'; +$lang->task->story = '相关需求'; +$lang->task->storyVersion = '需求版本'; +$lang->task->name = '任务名称'; +$lang->task->type = '任务类型'; +$lang->task->pri = '优先级'; +$lang->task->mailto = '抄送给'; +$lang->task->estimate = '最初预计'; +$lang->task->estimateAB = '预'; +$lang->task->left = '预计剩余'; +$lang->task->leftAB = '剩'; +$lang->task->consumed = '已经消耗'; +$lang->task->consumedAB = '耗'; +$lang->task->hour = '小时'; +$lang->task->estStarted = '预计开始'; +$lang->task->realStarted = '实际开始'; +$lang->task->deadline = '截止日期'; +$lang->task->deadlineAB = '截止'; +$lang->task->status = '任务状态'; +$lang->task->statusCustom = '状态排序'; +$lang->task->desc = '任务描述'; +$lang->task->assign = '指派'; +$lang->task->assignTo = $lang->task->assign; +$lang->task->assignedTo = '指派给'; +$lang->task->assignedToAB = '指派给'; +$lang->task->assignedDate = '指派日期'; +$lang->task->openedBy = '由谁创建'; +$lang->task->openedByAB = '创建者'; +$lang->task->openedDate = '创建日期'; +$lang->task->openedDateAB = '创建'; +$lang->task->finishedBy = '由谁完成'; +$lang->task->finishedByAB = '完成者'; +$lang->task->finishedDate = '完成时间'; +$lang->task->finishedDateAB = '完成'; +$lang->task->canceledBy = '由谁取消'; +$lang->task->canceledDate = '取消时间'; +$lang->task->closedBy = '由谁关闭'; +$lang->task->closedDate = '关闭时间'; +$lang->task->closedReason = '关闭原因'; +$lang->task->lastEditedBy = '最后修改'; +$lang->task->lastEditedDate = '最后修改日期'; +$lang->task->lastEdited = '最后编辑'; + +$lang->task->ditto = '同上'; + +$lang->task->statusList[''] = ''; +$lang->task->statusList['wait'] = '未开始'; +$lang->task->statusList['doing'] = '进行中'; +$lang->task->statusList['done'] = '已完成'; +$lang->task->statusList['cancel'] = '已取消'; +$lang->task->statusList['closed'] = '已关闭'; + +$lang->task->typeList[''] = ''; +$lang->task->typeList['design'] = '设计'; +$lang->task->typeList['devel'] = '开发'; +$lang->task->typeList['test'] = '测试'; +$lang->task->typeList['study'] = '研究'; +$lang->task->typeList['discuss'] = '讨论'; +$lang->task->typeList['ui'] = '界面'; +$lang->task->typeList['affair'] = '事务'; +$lang->task->typeList['misc'] = '其他'; + +$lang->task->priList[0] = ''; +$lang->task->priList[3] = '3'; +$lang->task->priList[1] = '1'; +$lang->task->priList[2] = '2'; +$lang->task->priList[4] = '4'; + +$lang->task->reasonList[''] = ''; +$lang->task->reasonList['done'] = '已完成'; +$lang->task->reasonList['cancel'] = '已取消'; + +$lang->task->afterChoices['continueAdding'] = '继续为该需求添加任务'; +$lang->task->afterChoices['toTastList'] = '返回任务列表'; +$lang->task->afterChoices['toStoryList'] = '返回需求列表'; + +$lang->task->buttonEdit = '编辑'; +$lang->task->buttonClose = '关闭'; +$lang->task->buttonCancel = '取消'; +$lang->task->buttonActivate = '激活'; +$lang->task->buttonLogEfforts = '记录工时'; +$lang->task->buttonDelete = '删除'; +$lang->task->buttonBackToList = '返回'; +$lang->task->buttonStart = '开始'; +$lang->task->buttonDone = '完成'; + +$lang->task->legendBasic = '基本信息'; +$lang->task->legendEffort = '工时信息'; +$lang->task->legendLife = '任务的一生'; +$lang->task->legendDesc = '任务描述'; +$lang->task->legendAction = '操作'; + +$lang->task->ajaxGetUserTasks = "接口:我的任务"; +$lang->task->ajaxGetProjectTasks = "接口:项目任务"; +$lang->task->confirmDelete = "您确定要删除这个任务吗?"; +$lang->task->copyStoryTitle = "同需求"; +$lang->task->afterSubmit = "添加之后"; +$lang->task->successSaved = "成功添加,"; +$lang->task->delayWarning = " 延期%s天 "; +$lang->task->remindBug = "该任务为Bug转化得到,是否更新Bug:%s ?"; + +/* 统计报表。*/ +$lang->task->report->common = '报表'; +$lang->task->report->select = '请选择报表类型'; +$lang->task->report->create = '生成报表'; + +$lang->task->report->charts['tasksPerProject'] = '项目任务数统计'; +$lang->task->report->charts['tasksPerModule'] = '模块任务数统计'; +$lang->task->report->charts['tasksPerAssignedTo'] = '指派给统计'; +$lang->task->report->charts['tasksPerType'] = '任务类型统计'; +$lang->task->report->charts['tasksPerPri'] = '优先级统计'; +$lang->task->report->charts['tasksPerStatus'] = '任务状态统计'; +$lang->task->report->charts['tasksPerDeadline'] = '截止日期统计'; +$lang->task->report->charts['tasksPerEstimate'] = '预计时间统计'; +$lang->task->report->charts['tasksPerLeft'] = '剩余时间统计'; +$lang->task->report->charts['tasksPerConsumed'] = '消耗时间统计'; +$lang->task->report->charts['tasksPerFinishedBy'] = '由谁完成统计'; +$lang->task->report->charts['tasksPerClosedReason'] = '关闭原因统计'; +$lang->task->report->charts['finishedTasksPerDay'] = '每天完成统计'; + +$lang->task->report->options->swf = 'pie2d'; +$lang->task->report->options->width = 'auto'; +$lang->task->report->options->height = 300; +$lang->task->report->options->graph->baseFontSize = 12; +$lang->task->report->options->graph->showNames = 1; +$lang->task->report->options->graph->formatNumber = 1; +$lang->task->report->options->graph->decimalPrecision = 0; +$lang->task->report->options->graph->animation = 0; +$lang->task->report->options->graph->rotateNames = 0; +$lang->task->report->options->graph->yAxisName = 'COUNT'; +$lang->task->report->options->graph->pieRadius = 100; // 饼图直径。 +$lang->task->report->options->graph->showColumnShadow = 0; // 是否显示柱状图阴影。 + +$lang->task->report->tasksPerProject->graph->xAxisName = '项目'; +$lang->task->report->tasksPerModule->graph->xAxisName = '模块'; +$lang->task->report->tasksPerAssignedTo->graph->xAxisName = '用户'; +$lang->task->report->tasksPerType->graph->xAxisName = '类型'; +$lang->task->report->tasksPerPri->graph->xAxisName = '优先级'; +$lang->task->report->tasksPerStatus->graph->xAxisName = '状态'; +$lang->task->report->tasksPerDeadline->graph->xAxisName = '日期'; +$lang->task->report->tasksPerEstimate->graph->xAxisName = '时间'; +$lang->task->report->tasksPerLeft->graph->xAxisName = '时间'; +$lang->task->report->tasksPerConsumed->graph->xAxisName = '时间'; +$lang->task->report->tasksPerFinishedBy->graph->xAxisName = '用户'; +$lang->task->report->tasksPerClosedReason->graph->xAxisName = '关闭原因'; + +$lang->task->report->finishedTasksPerDay->swf = 'column2d'; +$lang->task->report->finishedTasksPerDay->height = 400; +$lang->task->report->finishedTasksPerDay->graph->xAxisName = '日期'; +$lang->task->report->finishedTasksPerDay->graph->rotateNames = '1'; + +$lang->task->placeholder->estimate = '对该任务最初的预计'; +$lang->task->placeholder->mailto = '输入用户名进行选择'; diff --git a/trunk/module/task/lang/zh-tw.php b/trunk/module/task/lang/zh-tw.php new file mode 100644 index 0000000000..b0c1833e62 --- /dev/null +++ b/trunk/module/task/lang/zh-tw.php @@ -0,0 +1,187 @@ + + * @package task + * @version $Id: zh-tw.php 3476 2012-09-02 02:53:29Z wwccss $ + * @link http://www.zentao.net + */ +$lang->task->index = "任務一覽"; +$lang->task->create = "新增"; +$lang->task->batchCreate = "批量添加"; +$lang->task->batchEdit = "批量編輯"; +$lang->task->import = "導入之前未完任務"; +$lang->task->edit = "編輯"; +$lang->task->delete = "刪除"; +$lang->task->view = "查看任務"; +$lang->task->logEfforts = "記錄工時"; +$lang->task->start = "開始"; +$lang->task->finish = "完成"; +$lang->task->close = "關閉"; +$lang->task->batchClose = "批量關閉"; +$lang->task->cancel = "取消"; +$lang->task->activate = "激活"; +$lang->task->export = "導出"; +$lang->task->reportChart = "報表統計"; +$lang->task->fromBug = '來源Bug'; +$lang->task->confirmStoryChange = "確認需求變動"; + +$lang->task->common = '任務'; +$lang->task->id = '編號'; +$lang->task->project = '所屬項目'; +$lang->task->module = '所屬模組'; +$lang->task->story = '相關需求'; +$lang->task->storyVersion = '需求版本'; +$lang->task->name = '任務名稱'; +$lang->task->type = '任務類型'; +$lang->task->pri = '優先順序'; +$lang->task->mailto = '抄送給'; +$lang->task->estimate = '最初預計'; +$lang->task->estimateAB = '預'; +$lang->task->left = '預計剩餘'; +$lang->task->leftAB = '剩'; +$lang->task->consumed = '已經消耗'; +$lang->task->consumedAB = '耗'; +$lang->task->hour = '小時'; +$lang->task->estStarted = '預計開始'; +$lang->task->realStarted = '實際開始'; +$lang->task->deadline = '截止日期'; +$lang->task->deadlineAB = '截止'; +$lang->task->status = '任務狀態'; +$lang->task->statusCustom = '狀態排序'; +$lang->task->desc = '任務描述'; +$lang->task->assign = '指派'; +$lang->task->assignTo = $lang->task->assign; +$lang->task->assignedTo = '指派給'; +$lang->task->assignedToAB = '指派給'; +$lang->task->assignedDate = '指派日期'; +$lang->task->openedBy = '由誰創建'; +$lang->task->openedByAB = '創建者'; +$lang->task->openedDate = '創建日期'; +$lang->task->openedDateAB = '創建'; +$lang->task->finishedBy = '由誰完成'; +$lang->task->finishedByAB = '完成者'; +$lang->task->finishedDate = '完成時間'; +$lang->task->finishedDateAB = '完成'; +$lang->task->canceledBy = '由誰取消'; +$lang->task->canceledDate = '取消時間'; +$lang->task->closedBy = '由誰關閉'; +$lang->task->closedDate = '關閉時間'; +$lang->task->closedReason = '關閉原因'; +$lang->task->lastEditedBy = '最後修改'; +$lang->task->lastEditedDate = '最後修改日期'; +$lang->task->lastEdited = '最後編輯'; + +$lang->task->ditto = '同上'; + +$lang->task->statusList[''] = ''; +$lang->task->statusList['wait'] = '未開始'; +$lang->task->statusList['doing'] = '進行中'; +$lang->task->statusList['done'] = '已完成'; +$lang->task->statusList['cancel'] = '已取消'; +$lang->task->statusList['closed'] = '已關閉'; + +$lang->task->typeList[''] = ''; +$lang->task->typeList['design'] = '設計'; +$lang->task->typeList['devel'] = '開發'; +$lang->task->typeList['test'] = '測試'; +$lang->task->typeList['study'] = '研究'; +$lang->task->typeList['discuss'] = '討論'; +$lang->task->typeList['ui'] = '界面'; +$lang->task->typeList['affair'] = '事務'; +$lang->task->typeList['misc'] = '其他'; + +$lang->task->priList[0] = ''; +$lang->task->priList[3] = '3'; +$lang->task->priList[1] = '1'; +$lang->task->priList[2] = '2'; +$lang->task->priList[4] = '4'; + +$lang->task->reasonList[''] = ''; +$lang->task->reasonList['done'] = '已完成'; +$lang->task->reasonList['cancel'] = '已取消'; + +$lang->task->afterChoices['continueAdding'] = '繼續為該需求添加任務'; +$lang->task->afterChoices['toTastList'] = '返回任務列表'; +$lang->task->afterChoices['toStoryList'] = '返回需求列表'; + +$lang->task->buttonEdit = '編輯'; +$lang->task->buttonClose = '關閉'; +$lang->task->buttonCancel = '取消'; +$lang->task->buttonActivate = '激活'; +$lang->task->buttonLogEfforts = '記錄工時'; +$lang->task->buttonDelete = '刪除'; +$lang->task->buttonBackToList = '返回'; +$lang->task->buttonStart = '開始'; +$lang->task->buttonDone = '完成'; + +$lang->task->legendBasic = '基本信息'; +$lang->task->legendEffort = '工時信息'; +$lang->task->legendLife = '任務的一生'; +$lang->task->legendDesc = '任務描述'; +$lang->task->legendAction = '操作'; + +$lang->task->ajaxGetUserTasks = "介面:我的任務"; +$lang->task->ajaxGetProjectTasks = "介面:項目任務"; +$lang->task->confirmDelete = "您確定要刪除這個任務嗎?"; +$lang->task->copyStoryTitle = "同需求"; +$lang->task->afterSubmit = "添加之後"; +$lang->task->successSaved = "成功添加,"; +$lang->task->delayWarning = " 延期%s天 "; +$lang->task->remindBug = "該任務為Bug轉化得到,是否更新Bug:%s ?"; + +/* 統計報表。*/ +$lang->task->report->common = '報表'; +$lang->task->report->select = '請選擇報表類型'; +$lang->task->report->create = '生成報表'; + +$lang->task->report->charts['tasksPerProject'] = '項目任務數統計'; +$lang->task->report->charts['tasksPerModule'] = '模組任務數統計'; +$lang->task->report->charts['tasksPerAssignedTo'] = '指派給統計'; +$lang->task->report->charts['tasksPerType'] = '任務類型統計'; +$lang->task->report->charts['tasksPerPri'] = '優先順序統計'; +$lang->task->report->charts['tasksPerStatus'] = '任務狀態統計'; +$lang->task->report->charts['tasksPerDeadline'] = '截止日期統計'; +$lang->task->report->charts['tasksPerEstimate'] = '預計時間統計'; +$lang->task->report->charts['tasksPerLeft'] = '剩餘時間統計'; +$lang->task->report->charts['tasksPerConsumed'] = '消耗時間統計'; +$lang->task->report->charts['tasksPerFinishedBy'] = '由誰完成統計'; +$lang->task->report->charts['tasksPerClosedReason'] = '關閉原因統計'; +$lang->task->report->charts['finishedTasksPerDay'] = '每天完成統計'; + +$lang->task->report->options->swf = 'pie2d'; +$lang->task->report->options->width = 'auto'; +$lang->task->report->options->height = 300; +$lang->task->report->options->graph->baseFontSize = 12; +$lang->task->report->options->graph->showNames = 1; +$lang->task->report->options->graph->formatNumber = 1; +$lang->task->report->options->graph->decimalPrecision = 0; +$lang->task->report->options->graph->animation = 0; +$lang->task->report->options->graph->rotateNames = 0; +$lang->task->report->options->graph->yAxisName = 'COUNT'; +$lang->task->report->options->graph->pieRadius = 100; // 餅圖直徑。 +$lang->task->report->options->graph->showColumnShadow = 0; // 是否顯示柱狀圖陰影。 + +$lang->task->report->tasksPerProject->graph->xAxisName = '項目'; +$lang->task->report->tasksPerModule->graph->xAxisName = '模組'; +$lang->task->report->tasksPerAssignedTo->graph->xAxisName = '用戶'; +$lang->task->report->tasksPerType->graph->xAxisName = '類型'; +$lang->task->report->tasksPerPri->graph->xAxisName = '優先順序'; +$lang->task->report->tasksPerStatus->graph->xAxisName = '狀態'; +$lang->task->report->tasksPerDeadline->graph->xAxisName = '日期'; +$lang->task->report->tasksPerEstimate->graph->xAxisName = '時間'; +$lang->task->report->tasksPerLeft->graph->xAxisName = '時間'; +$lang->task->report->tasksPerConsumed->graph->xAxisName = '時間'; +$lang->task->report->tasksPerFinishedBy->graph->xAxisName = '用戶'; +$lang->task->report->tasksPerClosedReason->graph->xAxisName = '關閉原因'; + +$lang->task->report->finishedTasksPerDay->swf = 'column2d'; +$lang->task->report->finishedTasksPerDay->height = 400; +$lang->task->report->finishedTasksPerDay->graph->xAxisName = '日期'; +$lang->task->report->finishedTasksPerDay->graph->rotateNames = '1'; + +$lang->task->placeholder->estimate = '對該任務最初的預計'; +$lang->task->placeholder->mailto = '輸入用戶名進行選擇'; diff --git a/trunk/module/task/model.php b/trunk/module/task/model.php new file mode 100644 index 0000000000..8f81bd98a7 --- /dev/null +++ b/trunk/module/task/model.php @@ -0,0 +1,1102 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> +post->assignedTo as $assignedTo) + { + $task = fixer::input('post') + ->striptags('name') + ->add('project', (int)$projectID) + ->setDefault('estimate, left, story', 0) + ->setDefault('estStarted', '0000-00-00') + ->setDefault('deadline', '0000-00-00') + ->setDefault('status', 'wait') + ->setIF($this->post->estimate != false, 'left', $this->post->estimate) + ->setForce('assignedTo', $assignedTo) + ->setIF($this->post->story != false, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story)) + ->setDefault('openedBy', $this->app->user->account) + ->setDefault('openedDate', helper::now()) + ->remove('after,files,labels') + ->get(); + + if($assignedTo) $task->assignedDate = helper::now(); + + $this->setStatus($task); + + $this->dao->insert(TABLE_TASK)->data($task) + ->autoCheck() + ->batchCheck($this->config->task->create->requiredFields, 'notempty') + ->checkIF($task->estimate != '', 'estimate', 'float') + ->checkIF($task->deadline != '0000-00-00', 'deadline', 'ge', $task->estStarted) + ->exec(); + + if(!dao::isError()) + { + $taskID = $this->dao->lastInsertID(); + if($this->post->story) $this->loadModel('story')->setStage($this->post->story); + if(!empty($taskFile)) + { + $taskFile->objectID = $taskID; + $this->dao->insert(TABLE_FILE)->data($taskFile)->exec(); + } + else + { + $taskFileTitle = $this->loadModel('file')->saveUpload('task', $taskID); + $taskFile = $this->dao->select('*')->from(TABLE_FILE)->where('id')->eq(key($taskFileTitle))->fetch(); + unset($taskFile->id); + } + $tasksID[$assignedTo] = $taskID; + } + else + { + return false; + } + } + return $tasksID; + } + + /** + * Create a batch task. + * + * @param int $projectID + * @access public + * @return void + */ + public function batchCreate($projectID) + { + $now = helper::now(); + $tasks = fixer::input('post')->get(); + $mails = array(); + for($i = 0; $i < $this->config->task->batchCreate; $i++) + { + if($tasks->type[$i] != '' and $tasks->name[$i] != '' and $tasks->pri[$i] != 0) + { + $data[$i]->story = $tasks->story[$i] != 'ditto' ? $tasks->story[$i] : ($i == 0 ? 0 : $data[$i-1]->story); + $data[$i]->type = $tasks->type[$i] == 'ditto' ? ($i == 0 ? '' : $data[$i-1]->type) : $tasks->type[$i]; + $data[$i]->name = $tasks->name[$i]; + $data[$i]->desc = $tasks->desc[$i]; + $data[$i]->assignedTo = $tasks->assignedTo[$i]; + $data[$i]->pri = $tasks->pri[$i]; + $data[$i]->estimate = $tasks->estimate[$i]; + $data[$i]->left = $tasks->estimate[$i]; + $data[$i]->project = $projectID; + $data[$i]->deadline = '0000-00-00'; + $data[$i]->status = 'wait'; + $data[$i]->openedBy = $this->app->user->account; + $data[$i]->openedDate = $now; + $data[$i]->statusCustom = strpos(self::CUSTOM_STATUS_ORDER, 'wait') + 1; + if($tasks->story[$i] != '') $data[$i]->storyVersion = $this->loadModel('story')->getVersion($data[$i]->story); + if($tasks->assignedTo[$i] != '') $data[$i]->assignedDate = $now; + + $this->dao->insert(TABLE_TASK)->data($data[$i]) + ->autoCheck() + ->batchCheck($this->config->task->create->requiredFields, 'notempty') + ->checkIF($data[$i]->estimate != '', 'estimate', 'float') + ->exec(); + + if(dao::isError()) + { + echo js::error(dao::getError()); + die(js::reload('parent')); + } + + $taskID = $this->dao->lastInsertID(); + if($tasks->story[$i] != false) $this->story->setStage($tasks->story[$i]); + $actionID = $this->loadModel('action')->create('task', $taskID, 'Opened', ''); + $mails[$i]->taskID = $taskID; + $mails[$i]->actionID = $actionID; + } + else + { + unset($tasks->story[$i]); + unset($tasks->type[$i]); + unset($tasks->name[$i]); + unset($tasks->desc[$i]); + unset($tasks->assignedTo[$i]); + unset($tasks->pri[$i]); + unset($tasks->estimate[$i]); + } + } + return $mails; + } + + /** + * Update a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function update($taskID) + { + $oldTask = $this->getById($taskID); + $now = helper::now(); + $task = fixer::input('post') + ->striptags('name') + ->setDefault('story, estimate, left, consumed', 0) + ->setDefault('deadline', '0000-00-00') + ->setIF($this->post->story != false and $this->post->story != $oldTask->story, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story)) + + ->setIF($this->post->status == 'done', 'left', 0) + ->setIF($this->post->status == 'done' and !$this->post->finishedBy, 'finishedBy', $this->app->user->account) + ->setIF($this->post->status == 'done' and !$this->post->finishedDate, 'finishedDate', $now) + + ->setIF($this->post->status == 'cancel' and !$this->post->canceledBy, 'canceledBy', $this->app->user->account) + ->setIF($this->post->status == 'cancel' and !$this->post->canceledDate, 'canceledDate', $now) + ->setIF($this->post->status == 'cancel', 'assignedTo', $oldTask->openedBy) + ->setIF($this->post->status == 'cancel', 'assignedDate', $now) + + ->setIF($this->post->status == 'closed' and !$this->post->closedBy, 'closedBy', $this->app->user->account) + ->setIF($this->post->status == 'closed' and !$this->post->closedDate, 'closedDate', $now) + ->setIF($this->post->consumed > 0 and $this->post->left > 0 and $this->post->status == 'wait', 'status', 'doing') + + ->setIF($this->post->assignedTo != $oldTask->assignedTo, 'assignedDate', $now) + + ->setIF($this->post->status == 'wait' and $this->post->left == $oldTask->left and $this->post->consumed == 0, 'left', $this->post->estimate) + + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->remove('comment,files,labels') + ->get(); + $task->statusCustom = strpos(self::CUSTOM_STATUS_ORDER, $task->status) + 1; + + $this->dao->update(TABLE_TASK)->data($task) + ->autoCheck() + ->batchCheckIF($task->status != 'cancel', $this->config->task->edit->requiredFields, 'notempty') + + ->checkIF($task->estimate != false, 'estimate', 'float') + ->checkIF($task->left != false, 'left', 'float') + ->checkIF($task->consumed != false, 'consumed', 'float') + ->checkIF($task->status != 'wait' and $task->left == 0 and $task->status != 'cancel' and $task->status != 'closed', 'status', 'equal', 'done') + + ->batchCheckIF($task->status == 'wait' or $task->status == 'doing', 'finishedBy, finishedDate,canceledBy, canceledDate, closedBy, closedDate, closedReason', 'empty') + + ->checkIF($task->status == 'done', 'consumed', 'notempty') + ->checkIF($task->status == 'done' and $task->closedReason, 'closedReason', 'equal', 'done') + ->batchCheckIF($task->status == 'done', 'canceledBy, canceledDate', 'empty') + + ->checkIF($task->status == 'closed', 'closedReason', 'notempty') + ->batchCheckIF($task->closedReason == 'cancel', 'finishedBy, finishedDate', 'empty') + ->where('id')->eq((int)$taskID)->exec(); + + if($this->post->story != false) $this->loadModel('story')->setStage($this->post->story); + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Batch update task. + * + * @access public + * @return void + */ + public function batchUpdate() + { + $tasks = array(); + $allChanges = array(); + $now = helper::now(); + $taskIDList = $this->post->taskIDList ? $this->post->taskIDList : array(); + + /* Adjust whether the post data is complete, if not, remove the last element of $taskIDList. */ + if($this->session->showSuhosinInfo) array_pop($taskIDList); + + /* Initialize tasks from the post data.*/ + if(!empty($taskIDList)) + { + foreach($taskIDList as $taskID) + { + $oldTask = $this->getById($taskID); + + $task->name = htmlspecialchars($this->post->names[$taskID]); + $task->module = $this->post->modules[$taskID]; + $task->assignedTo = $this->post->assignedTos[$taskID]; + $task->type = $this->post->types[$taskID]; + $task->status = $this->post->statuses[$taskID]; + $task->pri = $this->post->pris[$taskID]; + $task->estimate = $this->post->estimates[$taskID]; + $task->consumed = $this->post->consumeds[$taskID]; + $task->left = $this->post->lefts[$taskID]; + $task->finishedBy = $this->post->finishedBys[$taskID]; + $task->canceledBy = $this->post->canceledBys[$taskID]; + $task->closedBy = $this->post->closedBys[$taskID]; + $task->closedReason = $this->post->closedReasons[$taskID]; + $task->finishedDate = ""; + $task->canceledDate = ""; + $task->closedDate = ""; + $task->lastEditedBy = $this->app->user->account; + $task->lastEditedDate = $now; + if(isset($this->post->assignedTos[$taskID])) + { + $task->assignedDate = $this->post->assignedTos[$taskID] == $oldTask->assignedTo ? $oldTask->assignedDate : $now; + } + + switch($task->status) + { + case 'done': + { + $task->left = 0; + if(!$task->finishedBy) $task->finishedBy = $this->app->user->account; + if(!$task->finishedDate) $task->finishedDate = $now; + } + break; + case 'cancel': + { + $task->assignedTo = $oldTask->openedBy; + $task->assignedDate = $now; + + if(!$task->canceledBy) $task->canceledBy = $this->app->user->account; + if(!$task->canceledDate) $task->canceledDate = $now; + } + break; + case 'closed': + { + if(!$task->closedBy) $task->closedBy = $this->app->user->account; + if(!$task->closedDate) $task->closedDate = $now; + } + break; + case 'wait': + { + if($task->consumed > 0 and $task->left > 0) $task->status = 'doing'; + if($task->left == $oldTask->left and $task->consumed == 0) $task->left = $task->estimate; + } + default:break; + } + if($task->assignedTo) $task->assignedDate = $now; + + $tasks[$taskID] = $task; + unset($task); + } + + /* Update task data. */ + foreach($tasks as $taskID => $task) + { + $oldTask = $this->getById($taskID); + $this->dao->update(TABLE_TASK)->data($task) + ->autoCheck() + ->batchCheckIF($task->status != 'cancel', $this->config->task->edit->requiredFields, 'notempty') + + ->checkIF($task->estimate != false, 'estimate', 'float') + ->checkIF($task->consumed != false, 'consumed', 'float') + ->checkIF($task->left != false, 'left', 'float') + ->checkIF($task->left == 0 and $task->status != 'cancel' and $task->status != 'closed' and $task->consumed != 0, 'status', 'equal', 'done') + + ->batchCheckIF($task->status == 'wait' or $task->status == 'doing', 'finishedBy, finishedDate,canceledBy, canceledDate, closedBy, closedDate, closedReason', 'empty') + + ->checkIF($task->status == 'done', 'consumed', 'notempty') + ->checkIF($task->status == 'done' and $task->closedReason, 'closedReason', 'equal', 'done') + ->batchCheckIF($task->status == 'done', 'canceledBy, canceledDate', 'empty') + + ->checkIF($task->status == 'closed', 'closedReason', 'notempty') + ->batchCheckIF($task->closedReason == 'cancel', 'finishedBy, finishedDate', 'empty') + ->where('id')->eq((int)$taskID) + ->exec(); + + if($oldTask->story != false) $this->loadModel('story')->setStage($oldTask->story); + if(!dao::isError()) + { + $allChanges[$taskID] = common::createChanges($oldTask, $task); + } + else + { + die(js::error('task#' . $taskID . dao::getError(true))); + } + } + } + + return $allChanges; + } + + /** + * Assign a task to a user again. + * + * @param int $taskID + * @access public + * @return void + */ + public function assign($taskID) + { + $now = helper::now(); + $oldTask = $this->getById($taskID); + $task = fixer::input('post') + ->cleanFloat('left') + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', $now) + ->remove('comment') + ->get(); + + $this->dao->update(TABLE_TASK) + ->data($task) + ->autoCheck() + ->check('left', 'float') + ->where('id')->eq($taskID)->exec(); + + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Start a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function start($taskID) + { + $oldTask = $this->getById($taskID); + $now = helper::now(); + $task = fixer::input('post') + ->setDefault('status', 'doing') + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', $now) + ->remove('comment')->get(); + $this->setStatus($task); + + $this->dao->update(TABLE_TASK)->data($task) + ->autoCheck() + ->check('consumed,left', 'float') + ->where('id')->eq((int)$taskID)->exec(); + + if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Finish a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function finish($taskID) + { + $oldTask = $this->getById($taskID); + $now = helper::now(); + $task = fixer::input('post') + ->setDefault('left', 0) + ->setDefault('assignedTo', $oldTask->openedBy) + ->setDefault('assignedDate', $now) + ->setDefault('status', 'done') + ->setDefault('finishedBy, lastEditedBy', $this->app->user->account) + ->setDefault('finishedDate, lastEditedDate', $now) + ->remove('comment')->get(); + $this->setStatus($task); + + $this->dao->update(TABLE_TASK)->data($task) + ->autoCheck() + ->check('consumed', 'notempty') + ->where('id')->eq((int)$taskID)->exec(); + + if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Close a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function close($taskID) + { + $oldTask = $this->getById($taskID); + $now = helper::now(); + $task = fixer::input('post') + ->setDefault('status', 'closed') + ->setDefault('assignedTo', 'closed') + ->setDefault('assignedDate', $now) + ->setDefault('closedBy, lastEditedBy', $this->app->user->account) + ->setDefault('closedDate, lastEditedDate', $now) + ->setIF($oldTask->status == 'done', 'closedReason', 'done') + ->setIF($oldTask->status == 'cancel', 'closedReason', 'cancel') + ->remove('_recPerPage') + ->remove('comment')->get(); + $this->setStatus($task); + + $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->where('id')->eq((int)$taskID)->exec(); + + if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Cancel a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function cancel($taskID) + { + $oldTask = $this->getById($taskID); + $now = helper::now(); + $task = fixer::input('post') + ->setDefault('status', 'cancel') + ->setDefault('assignedTo', $oldTask->openedBy) + ->setDefault('assignedDate', $now) + ->setDefault('finishedBy', '') + ->setDefault('finishedDate', '0000-00-00') + ->setDefault('canceledBy, lastEditedBy', $this->app->user->account) + ->setDefault('canceledDate, lastEditedDate', $now) + ->remove('comment')->get(); + $this->setStatus($task); + + $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->where('id')->eq((int)$taskID)->exec(); + + if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Activate a task. + * + * @param int $taskID + * @access public + * @return void + */ + public function activate($taskID) + { + $oldTask = $this->getById($taskID); + $task = fixer::input('post') + ->setDefault('left', 0) + ->setDefault('status', 'doing') + ->setDefault('finishedBy, canceledBy, closedBy, closedReason', '') + ->setDefault('finishedDate, canceledDate, closedDate', '0000-00-00') + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', helper::now()) + ->remove('comment')->get(); + $this->setStatus($task); + + $this->dao->update(TABLE_TASK)->data($task) + ->autoCheck() + ->check('left', 'notempty') + ->where('id')->eq((int)$taskID)->exec(); + + if($oldTask->story) $this->loadModel('story')->setStage($oldTask->story); + if(!dao::isError()) return common::createChanges($oldTask, $task); + + } + + /** + * Get task info by Id. + * + * @param int $taskID + * @param bool $setImgSize + * @access public + * @return object|bool + */ + public function getById($taskID, $setImgSize = false) + { + $task = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2') + ->on('t1.story = t2.id') + ->leftJoin(TABLE_USER)->alias('t3') + ->on('t1.assignedTo = t3.account') + ->where('t1.id')->eq((int)$taskID) + ->fetch(); + if(!$task) return false; + if($setImgSize) $task->desc = $this->loadModel('file')->setImgSize($task->desc); + if($task->assignedTo == 'closed') $task->assignedToRealName = 'Closed'; + foreach($task as $key => $value) if(strpos($key, 'Date') !== false and !(int)substr($value, 0, 4)) $task->$key = ''; + if($task->mailto) + { + $task->mailto = ltrim(trim($task->mailto), ','); // remove the first , + $task->mailto = str_replace(' ', '', $task->mailto); + $task->mailto = rtrim($task->mailto, ',') . ','; + $task->mailto = str_replace(',', ', ', $task->mailto); + } + $task->files = $this->loadModel('file')->getByObject('task', $taskID); + return $this->processTask($task); + } + + /** + * Get tasks list of a project. + * + * @param int $projectID + * @param array|string $moduleIds + * @param string $status + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getTasksByModule($projectID = 0, $moduleIds = 0, $orderBy = 'id_desc', $pager = null) + { + $orderBy = str_replace('status', 'statusCustom', $orderBy); + $tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_USER)->alias('t3')->on('t1.assignedTo = t3.account') + ->where('t1.project')->eq((int)$projectID) + ->beginIF(!empty($moduleIds))->andWhere('t1.module')->in($moduleIds)->fi() + ->andWhere('t1.deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task'); + + if($tasks) return $this->processTasks($tasks); + return array(); + } + + /** + * Get tasks of a project. + * + * @param int $projectID + * @param string $status all|needConfirm|wait|doing|done|cancel + * @param string $type + * @param object $pager + * @access public + * @return array + */ + public function getProjectTasks($projectID, $type = 'all', $orderBy = 'status_asc, id_desc', $pager = null) + { + $orderBy = str_replace('status', 'statusCustom', $orderBy); + $type = strtolower($type); + $tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_USER)->alias('t3')->on('t1.assignedTo = t3.account') + ->where('t1.project')->eq((int)$projectID) + ->andWhere('t1.deleted')->eq(0) + ->beginIF($type == 'needconfirm')->andWhere('t2.version > t1.storyVersion')->andWhere("t2.status = 'active'")->fi() + ->beginIF($type == 'assignedtome')->andWhere('t1.assignedTo')->eq($this->app->user->account)->fi() + ->beginIF($type == 'finishedbyme')->andWhere('t1.finishedby')->eq($this->app->user->account)->fi() + ->beginIF($type == 'delayed')->andWhere('deadline')->between('1970-1-1', helper::now())->andWhere('t1.status')->in('wait,doing')->fi() + ->beginIF(strpos('all|needconfirm|assignedtome|delayed|finishedbyme', $type) === false)->andWhere('t1.status')->in($type)->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task'); + + if($tasks) return $this->processTasks($tasks); + return array(); + } + + /** + * Get project tasks pairs. + * + * @param int $projectID + * @param string $status + * @param string $orderBy + * @access public + * @return array + */ + public function getProjectTaskPairs($projectID, $status = 'all', $orderBy = 'finishedBy, id_desc') + { + $tasks = array('' => ' '); + $stmt = $this->dao->select('t1.id, t1.name, t2.realname AS finishedByRealName') + ->from(TABLE_TASK)->alias('t1') + ->leftJoin(TABLE_USER)->alias('t2')->on('t1.finishedBy = t2.account') + ->where('t1.project')->eq((int)$projectID) + ->andWhere('t1.deleted')->eq(0) + ->beginIF($status != 'all')->andWhere('t1.status')->in($status)->fi() + ->orderBy($orderBy) + ->query(); + while($task = $stmt->fetch()) $tasks[$task->id] = "$task->id:$task->finishedByRealName:$task->name"; + return $tasks; + } + + /** + * Get tasks of a user. + * + * @param string $account + * @param string $type the query type + * @param int $limit + * @param object $pager + * @access public + * @return array + */ + public function getUserTasks($account, $type = 'assignedto', $limit = 0, $pager = null) + { + $type = strtolower($type); + $tasks = $this->dao->select('t1.*, t2.id as projectID, t2.name as projectName, t3.id as storyID, t3.title as storyTitle, t3.status AS storyStatus, t3.version AS latestStoryVersion') + ->from(TABLE_TASK)->alias('t1') + ->leftjoin(TABLE_PROJECT)->alias('t2') + ->on('t1.project = t2.id') + ->leftjoin(TABLE_STORY)->alias('t3') + ->on('t1.story = t3.id') + ->where('t1.deleted')->eq(0) + ->beginIF($type == 'openedby')->andWhere('t1.openedBy')->eq($account)->fi() + ->beginIF($type == 'assignedto')->andWhere('t1.assignedto')->eq($account)->fi() + ->beginIF($type == 'finishedby')->andWhere('t1.finishedby')->eq($account)->fi() + ->beginIF($type == 'closedby')->andWhere('t1.closedby')->eq($account)->fi() + ->beginIF($type == 'canceledby')->andWhere('t1.canceledby')->eq($account)->fi() + ->orderBy('id desc') + ->beginIF($limit > 0)->limit($limit)->fi() + ->page($pager) + ->fetchAll(); + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task'); + + if($tasks) return $this->processTasks($tasks); + return array(); + } + + /** + * Get tasks pairs of a user. + * + * @param string $account + * @param string $status + * @access public + * @return array + */ + public function getUserTaskPairs($account, $status = 'all') + { + $tasks = array(); + $sql = $this->dao->select('t1.id, t1.name, t2.name as project') + ->from(TABLE_TASK)->alias('t1') + ->leftjoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t1.assignedTo')->eq($account) + ->andWhere('t1.deleted')->eq(0); + if($status != 'all') $sql->andwhere('t1.status')->in($status); + $stmt = $sql->query(); + while($task = $stmt->fetch()) + { + $tasks[$task->id] = $task->project . ' / ' . $task->name; + } + return $tasks; + } + + /** + * Get task pairs of a story. + * + * @param int $storyID + * @param int $projectID + * @access public + * @return array + */ + public function getStoryTaskPairs($storyID, $projectID = 0) + { + return $this->dao->select('id, name') + ->from(TABLE_TASK) + ->where('story')->eq((int)$storyID) + ->andWhere('deleted')->eq(0) + ->beginIF($projectID)->andWhere('project')->eq($projectID)->fi() + ->fetchPairs(); + } + + /** + * Get counts of some stories' tasks. + * + * @param array $stories + * @param int $projectID + * @access public + * @return int + */ + public function getStoryTaskCounts($stories, $projectID = 0) + { + $taskCounts = $this->dao->select('story, COUNT(*) AS tasks') + ->from(TABLE_TASK) + ->where('story')->in($stories) + ->andWhere('deleted')->eq(0) + ->beginIF($projectID)->andWhere('project')->eq($projectID)->fi() + ->groupBy('story') + ->fetchPairs(); + foreach($stories as $storyID) if(!isset($taskCounts[$storyID])) $taskCounts[$storyID] = 0; + return $taskCounts; + } + + /** + * Batch process tasks. + * + * @param int $tasks + * @access private + * @return void + */ + public function processTasks($tasks) + { + $today = helper::today(); + foreach($tasks as $task) + { + /* Delayed or not. */ + if($task->status !== 'done' and $task->status !== 'cancel') + { + if($task->deadline != '0000-00-00') + { + $delay = helper::diffDate($today, $task->deadline); + if($delay > 0) $task->delay = $delay; + } + } + + /* Story changed or not. */ + $task->needConfirm = false; + if($task->storyStatus == 'active' and $task->latestStoryVersion > $task->storyVersion) + { + $task->needConfirm = true; + } + } + return $tasks; + } + + /** + * Process a task, judge it's status. + * + * @param object $task + * @access private + * @return object + */ + public function processTask($task) + { + $today = helper::today(); + + /* Delayed or not?. */ + if($task->status !== 'done' and $task->status !== 'cancel') + { + if($task->deadline != '0000-00-00') + { + if($task->status == 'closed' && $task->closedReason == 'done') + { + if($task->finishedDate == '') $delay = helper::diffDate(substr($task->closedDate, 0, 10), $task->deadline); + if($task->finishedDate != '') $delay = helper::diffDate(substr($task->finishedDate, 0, 10), $task->deadline); + } + else + { + $delay = helper::diffDate($today, $task->deadline); + } + if($delay > 0) $task->delay = $delay; + } + } + + /* Story changed or not. */ + $task->needConfirm = false; + if($task->storyStatus == 'active' and $task->latestStoryVersion > $task->storyVersion) + { + $task->needConfirm = true; + } + return $task; + } + + /** + * Set the status field of a task. + * + * @param object $task + * @access private + * @return void + */ + public function setStatus($task) + { + $task->statusCustom = strpos(self::CUSTOM_STATUS_ORDER, $task->status) + 1; + } + + /** + * Merge the default chart settings and the settings of current chart. + * + * @param string $chartType + * @access public + * @return void + */ + public function mergeChartOption($chartType) + { + $chartOption = $this->lang->task->report->$chartType; + $commonOption = $this->lang->task->report->options; + + $chartOption->graph->caption = $this->lang->task->report->charts[$chartType]; + if(!isset($chartOption->swf)) $chartOption->swf = $commonOption->swf; + if(!isset($chartOption->width)) $chartOption->width = $commonOption->width; + if(!isset($chartOption->height)) $chartOption->height = $commonOption->height; + + /* merge configuration */ + foreach($commonOption->graph as $key => $value) if(!isset($chartOption->graph->$key)) $chartOption->graph->$key = $value; + } + + /** + * Get report data of tasks per project + * + * @access public + * @return array + */ + public function getDataOftasksPerProject() + { + $datas = $this->dao->select('project as name, count(*) as value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('project') + ->orderBy('value DESC') + ->fetchAll('name'); + if(!$datas) return array(); + $projects = $this->loadModel('project')->getPairs('all'); + foreach($datas as $projectID => $data) $data->name = isset($projects[$projectID]) ? $projects[$projectID] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of tasks per module + * + * @access public + * @return array + */ + public function getDataOftasksPerModule() + { + $datas = $this->dao->select('module as name, count(*) as value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('module') + ->orderBy('value DESC') + ->fetchAll('name'); + if(!$datas) return array(); + $modules = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in(array_keys($datas))->fetchPairs(); + foreach($datas as $moduleID => $data) $data->name = isset($modules[$moduleID]) ? $modules[$moduleID] : '/'; + return $datas; + } + + /** + * Get report data of tasks per assignedTo + * + * @access public + * @return array + */ + public function getDataOftasksPerAssignedTo() + { + $datas = $this->dao->select('assignedTo AS name, count(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('assignedTo') + ->orderBy('value DESC') + ->fetchAll('name'); + if(!$datas) return array(); + if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); + foreach($datas as $account => $data) if(isset($this->users[$account])) $data->name = $this->users[$account]; + return $datas; + } + + /** + * Get report data of tasks per type + * + * @access public + * @return array + */ + public function getDataOftasksPerType() + { + $datas = $this->dao->select('type AS name, count(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('type') + ->orderBy('value DESC') + ->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $type => $data) if(isset($this->lang->task->typeList[$type])) $data->name = $this->lang->task->typeList[$type]; + return $datas; + } + + /** + * Get report data of tasks per priority + * + * @access public + * @return array + */ + public function getDataOftasksPerPri() + { + return $this->dao->select('pri AS name, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('pri') + ->orderBy('value DESC') + ->fetchAll('name'); + } + + /** + * Get report data of tasks per deadline + * + * @access public + * @return array + */ + public function getDataOftasksPerDeadline() + { + return $this->dao->select('deadline AS name, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('deadline') + ->orderBy('value DESC') + ->fetchAll('name'); + } + + /** + * Get report data of tasks per estimate + * + * @access public + * @return array + */ + public function getDataOftasksPerEstimate() + { + return $this->dao->select('estimate AS name, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('estimate') + ->orderBy('value DESC') + ->fetchAll('name'); + } + + /** + * Get report data of tasks per left + * + * @access public + * @return array + */ + public function getDataOftasksPerLeft() + { + return $this->dao->select('`left` AS name, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('`left`') + ->orderBy('value DESC') + ->fetchAll('name'); + } + + /** + * Get report data of tasks per consumed + * + * @access public + * @return array + */ + public function getDataOftasksPerConsumed() + { + return $this->dao->select('consumed AS name, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('consumed') + ->orderBy('value DESC') + ->fetchAll('name'); + } + + /** + * Get report data of tasks per finishedBy + * + * @access public + * @return array + */ + public function getDataOftasksPerFinishedBy() + { + $datas = $this->dao->select('finishedBy AS name, COUNT(finishedBy) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->andWhere('finishedBy')->ne('') + ->groupBy('finishedBy') + ->orderBy('value DESC') + ->fetchAll('name'); + if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); + foreach($datas as $account => $data) if(isset($this->users[$account])) $data->name = $this->users[$account]; + + return $datas; + } + + /** + * Get report data of tasks per closed reason + * + * @access public + * @return array + */ + public function getDataOftasksPerClosedReason() + { + $datas = $this->dao->select('closedReason AS name, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('closedReason') + ->orderBy('value DESC') + ->fetchAll('name'); + + foreach($datas as $closedReason => $data) + { + if(isset($this->lang->task->reasonList[$closedReason])) + { + $data->name = $this->lang->task->reasonList[$closedReason]; + } + } + return $datas; + } + + /** + * Get report data of finished tasks per day + * + * @access public + * @return array + */ + public function getDataOffinishedTasksPerDay() + { + $datas= $this->dao->select('DATE_FORMAT(finishedDate, "%Y-%m-%d") AS date, COUNT(*) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('date') + ->having('date != "0000-00-00"') + ->orderBy('finishedDate') + ->fetchAll(); + + /* Change data to name, because the task table has name field, conflicts. */ + foreach($datas as $data) + { + $data->name = $data->date; + unset($data->date); + } + return $datas; + } + + /** + * Get report data of status + * + * @access public + * @return array + */ + public function getDataOftasksPerStatus() + { + $datas = $this->dao->select('status AS name, COUNT(status) AS value') + ->from(TABLE_TASK)->alias('t1') + ->where($this->session->taskQueryCondition) + ->groupBy('status') + ->orderBy('value DESC') + ->fetchAll('name'); + if(!$datas)return array(); + foreach($datas as $status => $data) + { + $data->name = $this->lang->task->statusList[$status]; + } + return $datas; + } + + /** + * Judge an action is clickable or not. + * + * @param object $task + * @param string $action + * @access public + * @return bool + */ + public function isClickable($task, $action) + { + $action = strtolower($action); + + if($action == 'assignto') return $task->status != 'closed' and $task->status != 'cancel'; + if($action == 'start') return $task->status != 'doing' and $task->status != 'closed' and $task->status != 'cancel'; + if($action == 'finish') return $task->status != 'done' and $task->status != 'closed' and $task->status != 'cancel'; + if($action == 'close') return $task->status == 'done' or $task->status == 'cancel'; + if($action == 'activate') return $task->status == 'done' or $task->status == 'closed' or $task->status == 'cancel' ; + if($action == 'cancel') return $task->status != 'done ' and $task->status != 'closed' and $task->status != 'cancel'; + + return true; + } +} diff --git a/trunk/module/task/view/activate.html.php b/trunk/module/task/view/activate.html.php new file mode 100644 index 0000000000..c33c25105a --- /dev/null +++ b/trunk/module/task/view/activate.html.php @@ -0,0 +1,40 @@ + + * @package task + * @version $Id: start.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + +
      name;?>
      task->assignedTo;?>finishedBy, "class='select-3'");?>
      task->left;?>task->hour;?>
      comment;?>
      + goback, $this->session->taskList); + ?> +
      + +
      + diff --git a/trunk/module/task/view/assignto.html.php b/trunk/module/task/view/assignto.html.php new file mode 100644 index 0000000000..c18868a7a5 --- /dev/null +++ b/trunk/module/task/view/assignto.html.php @@ -0,0 +1,35 @@ + + * @package task + * @version $Id: complete.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + +
      name;?>
      task->assignedTo;?>
      task->left;?>left, "class='text-3'") . $lang->task->hour;?>
      comment;?>
      goback, $this->session->taskList);?>
      + +
      + diff --git a/trunk/module/task/view/batchcreate.html.php b/trunk/module/task/view/batchcreate.html.php new file mode 100644 index 0000000000..12010578aa --- /dev/null +++ b/trunk/module/task/view/batchcreate.html.php @@ -0,0 +1,48 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + task->batchCreate; $i++):?> + + task->typeList['ditto'] = $lang->task->ditto; $type = $i == 0 ? '' : 'ditto';?> + + + + + + + + + + + + + + +
      task->project . $lang->colon . $lang->task->batchCreate;?>
      idAB;?>task->story;?>task->name;?>typeAB;?>task->assignedTo;?>task->estimateAB;?>task->desc;?>task->pri;?>
      task->typeList, $type, 'class=select-1');?>task->priList, $pri, 'class=select-1');?>
      +
      + diff --git a/trunk/module/task/view/batchedit.html.php b/trunk/module/task/view/batchedit.html.php new file mode 100755 index 0000000000..8bbce7e7da --- /dev/null +++ b/trunk/module/task/view/batchedit.html.php @@ -0,0 +1,55 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->common . $lang->colon . $lang->task->batchEdit;?>
      idAB;?> task->name?>task->module;?>task->assignedTo;?>typeAB;?>task->status;?>task->pri;?>task->estimateAB?>task->consumedAB?>task->leftAB?>task->finishedBy;?>task->closedBy;?>task->closedReason;?>
      id . html::hidden("taskIDList[$task->id]", $task->id);?>id]", $task->name, 'class=text-1');?>id]", $modules, $task->module, 'class=select-1');?>id]", $members, $task->assignedTo, 'class=select-1');?>id]", $lang->task->typeList, $task->type, 'class=select-1');?>id]", $lang->task->statusList, $task->status, 'class=select-1');?>id]", (array)$lang->task->priList, $task->pri, 'class=select-1');?>id]", $task->estimate, 'class=text-1');?>id]", $task->consumed, 'class=text-1');?>id]", $task->left, 'class=text-1');?>id]", $members, $task->finishedBy, 'class=select-1');?>id]", $members, $task->closedBy, 'class=select-1');?>id]", $lang->task->reasonList, $task->closedReason, 'class=select-1');?>
      +
      + diff --git a/trunk/module/task/view/browse.html.php b/trunk/module/task/view/browse.html.php new file mode 100644 index 0000000000..43402665c4 --- /dev/null +++ b/trunk/module/task/view/browse.html.php @@ -0,0 +1,36 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + +
      task->browse;?>
      task->id;?>task->name;?>task->assignedTo;?>
      id;?>name;?>assignedTo;?>
      + createLink($this->moduleName, 'create', $vars); + echo "{$lang->task->create}"; + ?> +
      + diff --git a/trunk/module/task/view/cancel.html.php b/trunk/module/task/view/cancel.html.php new file mode 100644 index 0000000000..740a717826 --- /dev/null +++ b/trunk/module/task/view/cancel.html.php @@ -0,0 +1,25 @@ + + * @package task + * @version $Id: cancel.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + +
      name;?>
      comment;?>
      goback, $this->session->taskList);?>
      + +
      + diff --git a/trunk/module/task/view/close.html.php b/trunk/module/task/view/close.html.php new file mode 100644 index 0000000000..33627e458e --- /dev/null +++ b/trunk/module/task/view/close.html.php @@ -0,0 +1,27 @@ + + * @package task + * @version $Id: cancel.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + +
      name;?>
      comment;?>
      goback, $this->session->taskList);?>
      + +
      + diff --git a/trunk/module/task/view/create.html.php b/trunk/module/task/view/create.html.php new file mode 100644 index 0000000000..52973f37f3 --- /dev/null +++ b/trunk/module/task/view/create.html.php @@ -0,0 +1,105 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + task->create;?> +
      + id", $lang->project->importTask); + common::printLink('project', 'importBug', "projectID=$project->id", $lang->project->importBug); + ?> +
      +
      task->project;?>name;?>
      task->module;?>
      task->assignedTo;?>
      task->story;?> + + preview;?> +
      task->name;?> + task->copyStoryTitle, 'onclick=copyStoryTitle()');?> +
      task->desc;?>
      task->pri;?>task->priList, '', 'class=select-3');?> +
      task->estimate;?>task->hour;?>
      task->estStarted;?>
      task->deadline;?>
      task->type;?>task->typeList, '', 'class=select-3 onchange="setOwners(this.value)"');?>
      task->mailto;?>
      files;?>fetch('file', 'buildform');?>
      task->afterSubmit;?>task->afterChoices, 'continueAdding');?>
      +
      + diff --git a/trunk/module/task/view/edit.html.php b/trunk/module/task/view/edit.html.php new file mode 100644 index 0000000000..fdbe8370e3 --- /dev/null +++ b/trunk/module/task/view/edit.html.php @@ -0,0 +1,156 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
      +
      +
      TASK #id . $lang->colon . html::input('name', $task->name, 'class="text-1"');?>
      +
      +
      + + + + + + + +
      + + +
      +
      + task->desc;?> + desc), "rows='8' class='area-1'");?> +
      +
      + comment;?> + +
      +
      + files;?> + fetch('file', 'buildform');?> +
      +
      +
      goback, $this->inlink('view', "taskID=$task->id"));?>
      + +
      +
      + task->legendBasic;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->project;?>name;?>
      task->module;?>module, 'class="select-1"');?>
      task->story;?>story, 'class=select-1');?>
      task->assignedTo;?>assignedTo, 'class=select-1');?> +
      task->type;?>task->typeList, $task->type, 'class=select-1');?>
      task->status;?>task->statusList, $task->status, 'class=select-1');?>
      task->pri;?>task->priList, $task->pri, 'class=select-1');?> +
      task->mailto;?>mailto, 'class="text-1"');?>
      +
      +
      + task->legendEffort;?> + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->estStarted;?>estStarted, "class='text-1 date'");?>
      task->realStarted;?>realStarted, "class='text-1 date'");?>
      task->deadline;?>deadline, "class='text-1 date'");?>
      task->estimate;?>estimate, "class='text-1'");?>
      task->consumed;?>consumed, "class='text-1'");?>
      task->left;?>left, "class='text-1'");?>
      +
      +
      + task->legendLife;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->openedBy;?>openedBy];?>
      task->finishedBy;?>finishedBy, 'class=select-1');?>
      task->finishedDate;?>finishedDate, 'class="text-1"');?>
      task->canceledBy;?>canceledBy, 'class="select-1"');?>
      task->canceledDate;?>canceledDate, 'class="text-1"');?>
      task->closedBy;?>closedBy, 'class="select-1"');?>
      task->closedReason;?>task->reasonList, $task->closedReason, 'class="select-1"');?>
      task->closedDate;?>closedDate, 'class="text-1"');?>
      +
      +
      +
      + diff --git a/trunk/module/task/view/export.html.php b/trunk/module/task/view/export.html.php new file mode 100755 index 0000000000..81a6aeff3d --- /dev/null +++ b/trunk/module/task/view/export.html.php @@ -0,0 +1,13 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + diff --git a/trunk/module/task/view/finish.html.php b/trunk/module/task/view/finish.html.php new file mode 100644 index 0000000000..4e87ddb9d8 --- /dev/null +++ b/trunk/module/task/view/finish.html.php @@ -0,0 +1,36 @@ + + * @package task + * @version $Id: complete.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + + + + + + +
      name;?>
      task->consumed;?>consumed, "class='text-3'") . $lang->task->hour;?>
      task->finishedDate;?>
      comment;?>
      goback, $this->session->taskList);?>
      + +
      + diff --git a/trunk/module/task/view/import.html.php b/trunk/module/task/view/import.html.php new file mode 100644 index 0000000000..99527942ac --- /dev/null +++ b/trunk/module/task/view/import.html.php @@ -0,0 +1,58 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + assignedTo == $app->user->account ? 'style=color:red' : '';?> + + + + + + + + + + + + + + + +
      task->id;?>task->pri;?>task->name;?>task->assignedTo;?>task->estimate;?>task->consumed;?>task->left;?>task->type;?>task->status;?>task->story;?>actions;?>
      createLink('task', 'view', "task=$task->id"), sprintf('%03d', $task->id)); else printf('%03d', $task->id);?>pri;?>name;?>>assignedToRealName;?>estimate;?>consumed;?>left;?>task->typeList[$task->type];?>status;?> >task->statusList->{$task->status};?> + storyID) + { + if(common::hasPriv('story', 'view')) echo html::a($this->createLink('story', 'view', "storyid=$task->storyID"), $task->storyTitle); + else echo $task->storyTitle; + } + ?> + +
      + diff --git a/trunk/module/task/view/report.html.php b/trunk/module/task/view/report.html.php new file mode 100644 index 0000000000..fc38162b0c --- /dev/null +++ b/trunk/module/task/view/report.html.php @@ -0,0 +1,63 @@ + + * @package project + * @version $Id: report.html.php 1594 2011-04-10 11:00:00Z wj $ + * @link http://www.zentao.net + */ +?> + +
      +
      report->common;?>
      +
      goback); ?>
      +
      + + + + + + + +
      +
      task->report->select;?>
      +
      +
      + task->report->charts, $checkedCharts)?> + + +

      + task->report->create);?> +
      +
      +
      + + + $chartContent):?> + + + + + +
      task->report->common;?>
      + + + + + + + $data):?> + + + + + + +
      report->item;?>report->value;?>report->percent;?>
      name;?>value;?>percent * 100) . '%';?>
      +
      +
      + + diff --git a/trunk/module/task/view/sendmail.html.php b/trunk/module/task/view/sendmail.html.php new file mode 100644 index 0000000000..bcfe53cad9 --- /dev/null +++ b/trunk/module/task/view/sendmail.html.php @@ -0,0 +1,38 @@ + + * @package task + * @version $Id: sendmail.html.php 867 2010-06-17 09:32:58Z yuren_@126.com $ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + +
      + TASK #id . "=>$task->assignedTo " . html::a(common::getSysURL() . $this->createLink('task', 'view', "taskID=$task->id"), $task->name);?> +
      +
      + task->legendDesc;?> +
      + desc, '
      task->consumed;?>consumed, "class='text-2'") . $lang->task->hour;?>
      task->left;?>left, "class='text-2'") . $lang->task->hour;?>
      comment;?>
      goback, $this->session->taskList); ?>
      + + + diff --git a/trunk/module/task/view/view.html.php b/trunk/module/task/view/view.html.php new file mode 100644 index 0000000000..18fdaaf2e2 --- /dev/null +++ b/trunk/module/task/view/view.html.php @@ -0,0 +1,195 @@ + + * @package task + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +fromBug == 0):?> +
      '>TASK #id . ' ' . $task->name;?>
      + +
      '>TASK #id . ' ' . $task->name . '('. $lang->task->fromBug . $lang->colon . $task->fromBug . ')';?>
      + +
      + session->taskList != false ? $app->session->taskList : $this->createLink('project', 'browse', "projectID=$task->project"); + if(!$task->deleted) + { + ob_start(); + //if(!($task->status != 'closed' and $task->status != 'cancel' and common::printLink('task', 'logEfforts', "taskID=$task->id", $lang->task->buttonLogEfforts))) echo $lang->task->buttonLogEfforts . ' '; + common::printIcon('task', 'assignTo', "projectID=$task->project&taskID=$task->id"); + if($this->task->isClickable($task, 'start')) common::printIcon('task', 'start', "taskID=$task->id"); + if($this->task->isClickable($task, 'finish')) common::printIcon('task', 'finish', "taskID=$task->id"); + if($this->task->isClickable($task, 'close')) common::printIcon('task', 'close', "taskID=$task->id"); + if($this->task->isClickable($task, 'activate')) common::printIcon('task', 'activate', "taskID=$task->id"); + if($this->task->isClickable($task, 'cancel')) common::printIcon('task', 'cancel', "taskID=$task->id"); + + common::printDivider(); + common::printIcon('task', 'edit', "taskID=$task->id"); + common::printCommentIcon('task'); + common::printIcon('task', 'delete',"projectID=$task->project&taskID=$task->id", '', 'button', '', 'hiddenwin'); + + common::printDivider(); + common::printRPN($browseLink, $preAndNext); + + $actionLinks = ob_get_contents(); + ob_clean(); + echo $actionLinks; + } + ?> +
      +
      + + + + + + + +
      +
      + task->legendDesc;?> +
      desc;?>
      +
      + fetch('file', 'printFiles', array('files' => $task->files, 'fieldset' => 'true'));?> + + + +
      +
      + task->legendBasic;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->project;?>project", $project->name)) echo $project->name;?>
      task->module;?> + $module) + { + if(!common::printLink('project', 'task', "projectID=$task->project&browseType=byModule¶m=$module->id", $module->name)) echo $module->name; + if(isset($modulePath[$key + 1])) echo $lang->arrow; + } + ?> +
      task->story;?> + storyTitle and !common::printLink('story', 'view', "storyID=$task->story", $task->storyTitle)) echo $task->storyTitle; + if($task->needConfirm) + { + echo "({$lang->story->changed} "; + echo html::a($this->createLink('task', 'confirmStoryChange', "taskID=$task->id"), $lang->confirm, 'hiddenwin'); + echo ")"; + } + ?> +
      task->assignedTo;?>assignedToRealName . $lang->at . $task->assignedDate;?>
      task->type;?>task->typeList[$task->type];?>
      task->status;?>show($lang->task->statusList, $task->status);?>
      task->pri;?>show($lang->task->priList, $task->pri);?>
      task->mailto;?>mailto)); foreach($mailto as $account) echo ' ' . $users[$account]; ?>
      +
      +
      + task->legendEffort;?> + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->estStarted;?>estStarted;?>
      task->realStarted;?>realStarted; ?>
      task->deadline;?> + deadline; + if(isset($task->delay)) printf($lang->task->delayWarning, $task->delay); + ?> +
      task->estimate;?>estimate . $lang->workingHour;?>
      task->consumed;?>consumed . $lang->workingHour;?>
      task->left;?>left . $lang->workingHour;?>
      +
      +
      + task->legendLife;?> + + + + + + + + + + + + + + + + + + + + + + + + + +
      task->openedBy;?>openedBy) echo $users[$task->openedBy] . $lang->at . $task->openedDate;?>
      task->finishedBy;?>finishedBy) echo $users[$task->finishedBy] . $lang->at . $task->finishedDate;?>
      task->canceledBy;?>canceledBy) echo $users[$task->canceledBy] . $lang->at . $task->canceledDate;?>
      task->closedBy;?>closedBy) echo $users[$task->closedBy] . $lang->at . $task->closedDate;?>
      task->closedReason;?>task->reasonList[$task->closedReason];?>
      task->lastEdited;?>lastEditedBy) echo $users[$task->lastEditedBy] . $lang->at . $task->lastEditedDate;?>
      +
      +
      + diff --git a/trunk/module/testcase/config.php b/trunk/module/testcase/config.php new file mode 100644 index 0000000000..89b3fef2f1 --- /dev/null +++ b/trunk/module/testcase/config.php @@ -0,0 +1,46 @@ +testcase->search['module'] = 'testcase'; +$config->testcase->search['fields']['title'] = $lang->testcase->title; +$config->testcase->search['fields']['id'] = $lang->testcase->id; +$config->testcase->search['fields']['module'] = $lang->testcase->module; +$config->testcase->search['fields']['keywords'] = $lang->testcase->keywords; +$config->testcase->search['fields']['lastEditedBy'] = $lang->testcase->lastEditedByAB; +$config->testcase->search['fields']['type'] = $lang->testcase->type; + +$config->testcase->search['fields']['pri'] = $lang->testcase->pri; +$config->testcase->search['fields']['openedBy'] = $lang->testcase->openedBy; +$config->testcase->search['fields']['status'] = $lang->testcase->status; +$config->testcase->search['fields']['product'] = $lang->testcase->product; +$config->testcase->search['fields']['stage'] = $lang->testcase->stage; + +$config->testcase->search['fields']['openedDate'] = $lang->testcase->openedDate; +$config->testcase->search['fields']['lastEditedDate'] = $lang->testcase->lastEditedDateAB; + +$config->testcase->search['params']['title'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->testcase->search['params']['module'] = array('operator' => 'belong', 'control' => 'select', 'values' => 'modules'); +$config->testcase->search['params']['keywords'] = array('operator' => 'include', 'control' => 'input', 'values' => ''); +$config->testcase->search['params']['lastEditedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->testcase->search['params']['type'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->testcase->typeList); + +$config->testcase->search['params']['pri'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->testcase->priList); +$config->testcase->search['params']['openedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users'); +$config->testcase->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->testcase->statusList); +$config->testcase->search['params']['product'] = array('operator' => '=', 'control' => 'select', 'values' => ''); +$config->testcase->search['params']['stage'] = array('operator' => 'include', 'control' => 'select', 'values' => $lang->testcase->stageList); + +$config->testcase->search['params']['openedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); +$config->testcase->search['params']['lastEditedDate'] = array('operator' => '>=', 'control' => 'input', 'values' => '', 'class' => 'date'); + +$config->testcase->defaultSteps = 3; +$config->testcase->batchCreate = 10; + +$config->testcase->create->requiredFields = 'title,type'; +$config->testcase->edit->requiredFields = 'title,type'; + +$config->testcase->exportFields = ' + id, product, module, story, + title, precondition, stepDesc, stepExpect, keywords, + pri, type, stage, status, frequency, + openedBy, openedDate, lastEditedBy, lastEditedDate, + version,linkCase'; diff --git a/trunk/module/testcase/control.php b/trunk/module/testcase/control.php new file mode 100644 index 0000000000..b48bdc092f --- /dev/null +++ b/trunk/module/testcase/control.php @@ -0,0 +1,562 @@ + + * @package case + * @version $Id$ + * @link http://www.zentao.net + */ +class testcase extends control +{ + public $products = array(); + + /** + * Construct function, load product, tree, user auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('product'); + $this->loadModel('tree'); + $this->loadModel('user'); + $this->view->products = $this->products = $this->product->getPairs(); + } + + /** + * Index page. + * + * @access public + * @return void + */ + public function index() + { + $this->locate($this->createLink('testcase', 'browse')); + } + + /** + * Browse cases. + * + * @param int $productID + * @param string $browseType + * @param int $param + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browse($productID = 0, $browseType = 'byModule', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Set browseType, productID, moduleID and queryID. */ + $browseType = strtolower($browseType); + $productID = $this->product->saveState($productID, $this->products); + $moduleID = ($browseType == 'bymodule') ? (int)$param : 0; + $queryID = ($browseType == 'bysearch') ? (int)$param : 0; + + /* Set menu, save session. */ + $this->testcase->setMenu($this->products, $productID); + $this->session->set('caseList', $this->app->getURI(true)); + $this->session->set('productID', $productID); + $this->session->set('moduleID', $moduleID); + $this->session->set('browseType', $browseType); + $this->session->set('orderBy', $orderBy); + + /* Load lang. */ + $this->app->loadLang('testtask'); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + /* By module or all cases. */ + if($browseType == 'bymodule' or $browseType == 'all') + { + $childModuleIds = $this->tree->getAllChildId($moduleID); + $this->view->cases = $this->testcase->getModuleCases($productID, $childModuleIds, $orderBy, $pager); + } + /* Cases need confirmed. */ + elseif($browseType == 'needconfirm') + { + $this->view->cases = $this->dao->select('t1.*, t2.title AS storyTitle')->from(TABLE_CASE)->alias('t1')->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->where("t2.status = 'active'") + ->andWhere('t1.deleted')->eq(0) + ->andWhere('t2.version > t1.storyVersion') + ->orderBy($orderBy) + ->fetchAll(); + } + /* By search. */ + elseif($browseType == 'bysearch') + { + if($queryID) + { + $query = $this->loadModel('search')->getQuery($queryID); + if($query) + { + $this->session->set('testcaseQuery', $query->sql); + $this->session->set('testcaseForm', $query->form); + } + else + { + $this->session->set('testcaseQuery', ' 1 = 1'); + } + } + else + { + if($this->session->testcaseQuery == false) $this->session->set('testcaseQuery', ' 1 = 1'); + } + + $caseQuery = str_replace("`product` = 'all'", '1', $this->session->testcaseQuery); // If product is all, change it to 1=1. + $this->view->cases = $this->dao->select('*')->from(TABLE_CASE)->where($caseQuery) + ->andWhere('product')->eq($productID) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + + /* save session .*/ + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase'); + + /* Build the search form. */ + $this->config->testcase->search['params']['product']['values']= array($productID => $this->products[$productID], 'all' => $this->lang->testcase->allProduct); + $this->config->testcase->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'case'); + $this->config->testcase->search['actionURL'] = $this->createLink('testcase', 'browse', "productID=$productID&browseType=bySearch&queryID=myQueryID"); + $this->config->testcase->search['queryID'] = $queryID; + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->testcase->search); + + /* Assign. */ + $this->view->header->title = $this->products[$productID] . $this->lang->colon . $this->lang->testcase->common; + $this->view->position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testcase->common; + $this->view->productID = $productID; + $this->view->productName = $this->products[$productID]; + $this->view->moduleTree = $this->tree->getTreeMenu($productID, $viewType = 'case', $startModuleID = 0, array('treeModel', 'createCaseLink')); + $this->view->moduleID = $moduleID; + $this->view->pager = $pager; + $this->view->users = $this->user->getPairs('noletter'); + $this->view->orderBy = $orderBy; + $this->view->browseType = $browseType; + $this->view->param = $param; + $this->view->treeClass = $browseType == 'bymodule' ? '' : 'hidden'; + + $this->display(); + } + + /** + * Create a test case. + * + * @param int $productID + * @param int $moduleID + * @param string $from + * @param int $param + * @access public + * @return void + */ + public function create($productID, $moduleID = 0, $from = '', $param = 0, $storyID = 0) + { + $testcaseID = $from == 'testcase' ? $param : 0; + $bugID = $from == 'bug' ? $param : 0; + + $this->loadModel('story'); + if(!empty($_POST)) + { + $caseID = $this->testcase->create($bugID); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action'); + $this->action->create('case', $caseID, 'Opened'); + die(js::locate($this->createLink('testcase', 'browse', "productID=$_POST[product]&browseType=byModule¶m=$_POST[module]"), 'parent')); + } + if(empty($this->products)) $this->locate($this->createLink('product', 'create')); + + /* Set productID and currentModuleID. */ + $productID = $this->product->saveState($productID, $this->products); + $currentModuleID = (int)$moduleID; + + /* Set menu. */ + $this->testcase->setMenu($this->products, $productID); + + /* Init vars. */ + $type = 'feature'; + $stage = ''; + $pri = 0; + $title = ''; + $precondition = ''; + $keywords = ''; + $steps = array(); + + /* If testcaseID large than 0, use this testcase as template. */ + if($testcaseID > 0) + { + $testcase = $this->testcase->getById($testcaseID); + $productID = $testcase->product; + $type = $testcase->type ? $testcase->type : 'feature'; + $stage = $testcase->stage; + $pri = $testcase->pri; + $storyID = $testcase->story; + $title = $testcase->title; + $precondition = $testcase->precondition; + $keywords = $testcase->keywords; + $steps = $testcase->steps; + } + + /* If bugID large than 0, use this bug as template. */ + if($bugID > 0) + { + $bug = $this->loadModel('bug')->getById($bugID); + $type = $bug->type; + $pri = $bug->pri ? $bug->pri : $bug->severity; + $storyID = $bug->story; + $title = $bug->title; + $keywords = $bug->keywords; + $steps = $this->testcase->createStepsFromBug($bug->steps); + } + + /* Padding the steps to the default steps count. */ + if(count($steps) < $this->config->testcase->defaultSteps) + { + $paddingCount = $this->config->testcase->defaultSteps - count($steps); + $step->desc = ''; + $step->expect = ''; + for($i = 1; $i <= $paddingCount; $i ++) $steps[] = $step; + } + + $header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testcase->create; + $position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $this->products[$productID]); + $position[] = $this->lang->testcase->create; + + $users = $this->user->getPairs(); + $this->view->header = $header; + $this->view->position = $position; + $this->view->productID = $productID; + $this->view->users = $users; + $this->view->productName = $this->products[$productID]; + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0); + $this->view->currentModuleID = $currentModuleID; + $this->view->stories = $this->story->getProductStoryPairs($productID); + $this->view->type = $type; + $this->view->stage = $stage; + $this->view->pri = $pri; + $this->view->storyID = $storyID; + $this->view->title = $title; + $this->view->precondition = $precondition; + $this->view->keywords = $keywords; + $this->view->steps = $steps; + + $this->display(); + } + + + /** + * Create a batch test case. + * + * @param int $productID + * @param int $moduleID + * @param int $testcaseID + * @access public + * @return void + */ + public function batchCreate($productID, $moduleID = 0) + { + $this->loadModel('story'); + if(!empty($_POST)) + { + $caseID = $this->testcase->batchCreate($productID); + if(dao::isError()) die(js::error(dao::getError())); + die(js::locate($this->createLink('testcase', 'browse', "productID=$_POST[product]&browseType=byModule¶m=$_POST[module]"), 'parent')); + } + if(empty($this->products)) $this->locate($this->createLink('product', 'create')); + + /* Set productID and currentModuleID. */ + $productID = $this->product->saveState($productID, $this->products); + $currentModuleID = (int)$moduleID; + + /* Set menu. */ + $this->testcase->setMenu($this->products, $productID); + + /* Init vars. */ + $type = 'feature'; + $title = ''; + + $header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testcase->batchCreate; + $position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $this->products[$productID]); + $position[] = $this->lang->testcase->batchCreate; + + $users = $this->user->getPairs(); + $this->view->header = $header; + $this->view->position = $position; + $this->view->productID = $productID; + $this->view->users = $users; + $this->view->productName = $this->products[$productID]; + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0); + $this->view->currentModuleID = $currentModuleID; + $this->view->stories = $this->story->getProductStoryPairs($productID); + $this->view->type = $type; + $this->view->title = $title; + + $this->display(); + } + + /** + * View a test case. + * + * @param int $caseID + * @param int $version + * @access public + * @return void + */ + public function view($caseID, $version = 0) + { + $case = $this->testcase->getById($caseID, $version); + if(!$case) die(js::error($this->lang->notFound) . js::locate('back')); + + $productID = $case->product; + $this->testcase->setMenu($this->products, $productID); + + $this->view->header['title'] = "CASE #$case->id $case->title - " . $this->products[$productID]; + $this->view->position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testcase->view; + + $this->view->case = $case; + $this->view->productName = $this->products[$productID]; + $this->view->modulePath = $this->tree->getParents($case->module); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->actions = $this->loadModel('action')->getList('case', $caseID); + $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('testcase', $caseID); + + $this->display(); + } + + /** + * Edit a case. + * + * @param int $caseID + * @access public + * @return void + */ + public function edit($caseID, $comment = false) + { + $this->loadModel('story'); + + if(!empty($_POST)) + { + $changes = array(); + $files = array(); + if($comment == false) + { + $changes = $this->testcase->update($caseID); + if(dao::isError()) die(js::error(dao::getError())); + $files = $this->loadModel('file')->saveUpload('testcase', $caseID); + } + if($this->post->comment != '' or !empty($changes) or !empty($files)) + { + $this->loadModel('action'); + $action = !empty($changes) ? 'Edited' : 'Commented'; + $fileAction = ''; + if(!empty($files)) $fileAction = $this->lang->addFiles . join(',', $files) . "\n"; + $actionID = $this->action->create('case', $caseID, $action, $fileAction . $this->post->comment); + $this->action->logHistory($actionID, $changes); + } + die(js::locate($this->createLink('testcase', 'view', "caseID=$caseID"), 'parent')); + } + + $case = $this->testcase->getById($caseID); + if(empty($case->steps)) + { + $step->desc = ''; + $step->expect = ''; + $case->steps[] = $step; + } + $productID = $case->product; + $currentModuleID = $case->module; + $header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testcase->edit; + $position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $this->products[$productID]); + $position[] = $this->lang->testcase->edit; + + /* Set menu. */ + $this->testcase->setMenu($this->products, $productID); + + $users = $this->user->getPairs(); + $this->view->header = $header; + $this->view->position = $position; + $this->view->productID = $productID; + $this->view->productName = $this->products[$productID]; + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0); + $this->view->currentModuleID = $currentModuleID; + $this->view->users = $users; + $this->view->stories = $this->story->getProductStoryPairs($productID); + + $this->view->header = $header; + $this->view->position = $position; + $this->view->case = $case; + $this->view->actions = $this->loadModel('action')->getList('case', $caseID); + + $this->display(); + } + + /** + * Delete a test case + * + * @param int $caseID + * @param string $confirm yes|noe + * @access public + * @return void + */ + public function delete($caseID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->testcase->confirmDelete, inlink('delete', "caseID=$caseID&confirm=yes"))); + } + else + { + $this->testcase->delete(TABLE_CASE, $caseID); + die(js::locate($this->session->caseList, 'parent')); + } + } + + /** + * Confirm story changes. + * + * @param int $caseID + * @access public + * @return void + */ + public function confirmStoryChange($caseID) + { + $case = $this->testcase->getById($caseID); + $this->dao->update(TABLE_CASE)->set('storyVersion')->eq($case->latestStoryVersion)->where('id')->eq($caseID)->exec(); + $this->loadModel('action')->create('case', $caseID, 'confirmed', '', $case->latestStoryVersion); + die(js::reload('parent')); + } + + /** + * export + * + * @param int $productID + * @param string $orderBy + * @param int $taskID + * @access public + * @return void + */ + public function export($productID, $orderBy, $taskID = 0) + { + if($_POST) + { + $caseLang = $this->lang->testcase; + $caseConfig = $this->config->testcase; + + /* Create field lists. */ + $fields = explode(',', $caseConfig->exportFields); + foreach($fields as $key => $fieldName) + { + $fieldName = trim($fieldName); + $fields[$fieldName] = isset($caseLang->$fieldName) ? $caseLang->$fieldName : $fieldName; + unset($fields[$key]); + } + + /* Get cases. */ + if($taskID) + { + $caseIDList = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->fetchPairs(); + $cases = $this->dao->select('*')->from(TABLE_CASE)->where($this->session->testcaseQueryCondition)->andWhere('id')->in($caseIDList)->orderBy($orderBy)->fetchAll('id'); + } + else + { + $cases = $this->dao->select('*')->from(TABLE_CASE)->where($this->session->testcaseQueryCondition)->orderBy($orderBy)->fetchAll('id'); + } + + /* Get users, products and projects. */ + $users = $this->loadModel('user')->getPairs('noletter'); + $products = $this->loadModel('product')->getPairs(); + + /* Get related objects id lists. */ + $relatedModuleIdList = array(); + $relatedStoryIdList = array(); + $relatedCaseIdList = array(); + + foreach($cases as $case) + { + $relatedModuleIdList[$case->module] = $case->module; + $relatedStoryIdList[$case->story] = $case->story; + $relatedCaseIdList[$case->linkCase] = $case->linkCase; + + /* Process link cases. */ + $linkCases = explode(',', $case->linkCase); + foreach($linkCases as $linkCaseID) + { + if($linkCaseID) $relatedCaseIdList[$linkCaseID] = trim($linkCaseID); + } + } + + /* Get related objects title or names. */ + $relatedModules = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in($relatedModuleIdList)->fetchPairs(); + $relatedStories = $this->dao->select('id,title')->from(TABLE_STORY) ->where('id')->in($relatedStoryIdList)->fetchPairs(); + $relatedCases = $this->dao->select('id, title')->from(TABLE_CASE)->where('id')->in($relatedCaseIdList)->fetchPairs(); + $relatedSteps = $this->dao->select('`case`, version, `desc`, expect')->from(TABLE_CASESTEP)->where('`case`')->in(@array_keys($cases))->orderBy('version desc,id')->fetchGroup('case'); + $relatedModules = array('0' => '/') + $relatedModules; + + foreach($cases as $case) + { + $case->stepDesc = ''; + $case->stepExpect = ''; + if(isset($relatedSteps[$case->id])) + { + $i = 1; + foreach($relatedSteps[$case->id] as $step) + { + if($step->version != $case->version) continue; + $sign = (in_array($this->post->fileType, array('html', 'xml'))) ? '
      ' : "\n"; + $case->stepDesc .= $i . ". " . $step->desc . $sign; + $case->stepExpect .= $i . ". " . $step->expect . $sign; + $i ++; + } + } + + if($this->post->fileType == 'csv') + { + $case->stepDesc = str_replace('"', '""', $case->stepDesc); + $case->stepExpect = str_replace('"', '""', $case->stepExpect); + } + + /* fill some field with useful value. */ + if(isset($products[$case->product])) $case->product = $products[$case->product]; + if(isset($relatedModules[$case->module])) $case->module = $relatedModules[$case->module]; + if(isset($relatedStories[$case->story])) $case->story = $relatedStories[$case->story]; + + if(isset($caseLang->priList[$case->pri])) $case->pri = $caseLang->priList[$case->pri]; + if(isset($caseLang->typeList[$case->type])) $case->type = $caseLang->typeList[$case->type]; + if(isset($caseLang->stageList[$case->stage])) $case->stage = $caseLang->stageList[$case->stage]; + if(isset($caseLang->statusList[$case->status])) $case->status = $caseLang->statusList[$case->status]; + if(isset($users[$case->openedBy])) $case->openedBy = $users[$case->openedBy]; + if(isset($users[$case->lastEditedBy])) $case->lastEditedBy = $users[$case->lastEditedBy]; + + $case->openedDate = substr($case->openedDate, 0, 10); + $case->lastEditedDate = substr($case->lastEditedDate, 0, 10); + + if($case->linkCase) + { + $tmpLinkCases = array(); + $linkCaseIdList = explode(',', $case->linkCase); + foreach($linkCaseIdList as $linkCaseID) + { + $linkCaseID = trim($linkCaseID); + $tmpLinkCases[] = isset($relatedCases[$linkCaseID]) ? $relatedCases[$linkCaseID] : $linkCaseID; + } + $case->linkCase = join("; \n", $tmpLinkCases); + } + } + + $this->post->set('fields', $fields); + $this->post->set('rows', $cases); + $this->post->set('kind', 'testcase'); + $this->fetch('file', 'export2' . $this->post->fileType, $_POST); + } + + $this->display(); + } +} diff --git a/trunk/module/testcase/css/batchcreate.css b/trunk/module/testcase/css/batchcreate.css new file mode 100644 index 0000000000..cbefbcd7df --- /dev/null +++ b/trunk/module/testcase/css/batchcreate.css @@ -0,0 +1,5 @@ +.text-1, .select-1{height:30px} +.w-300px{width:300px} +.w-180px{width:180px} +.half-left {text-align:right} +.half-right{text-align:left} diff --git a/trunk/module/testcase/css/batchedit.css b/trunk/module/testcase/css/batchedit.css new file mode 100755 index 0000000000..10b4561986 --- /dev/null +++ b/trunk/module/testcase/css/batchedit.css @@ -0,0 +1 @@ +.w-340px {width:340px} diff --git a/trunk/module/testcase/css/browse.css b/trunk/module/testcase/css/browse.css new file mode 100644 index 0000000000..a83175e923 --- /dev/null +++ b/trunk/module/testcase/css/browse.css @@ -0,0 +1 @@ +.w-220px{width:220px} diff --git a/trunk/module/testcase/css/edit.css b/trunk/module/testcase/css/edit.css new file mode 100644 index 0000000000..0901de97b6 --- /dev/null +++ b/trunk/module/testcase/css/edit.css @@ -0,0 +1 @@ +#module{width:90%} diff --git a/trunk/module/testcase/js/batchcreate.js b/trunk/module/testcase/js/batchcreate.js new file mode 100755 index 0000000000..082e595fde --- /dev/null +++ b/trunk/module/testcase/js/batchcreate.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + for(i = 0; i < testcaseBatchCreateNum; i++) $("#story" + i).chosen({no_results_text: noResultsMatch}); +}); diff --git a/trunk/module/testcase/js/browse.js b/trunk/module/testcase/js/browse.js new file mode 100644 index 0000000000..9fda8fcd75 --- /dev/null +++ b/trunk/module/testcase/js/browse.js @@ -0,0 +1,33 @@ +/* Switch to module browse. */ +function browseByModule(active) +{ + $('.side').removeClass('hidden'); + $('.divider').removeClass('hidden'); + $('#bymoduleTab').addClass('active'); + $('#' + active + 'Tab').removeClass('active'); + $('#bysearchTab').removeClass('active'); + $('#querybox').addClass('hidden'); +} + +/* Swtich to search module. */ +function browseBySearch(active) +{ + $('#querybox').removeClass('hidden'); + $('.side').addClass('hidden'); + $('.divider').addClass('hidden'); + $('#' + active + 'Tab').removeClass('active'); + $('#bysearchTab').addClass('active'); + $('#bymoduleTab').removeClass('active'); +} + +$(document).ready(function() +{ + $("a.runcase").colorbox({width:900, height:600, iframe:true, transition:'none'}); + $('#' + browseType + 'Tab').addClass('active'); + $('#module' + moduleID).addClass('active'); +}); + +$(document).ready(function() +{ + $(".results").colorbox({width:900, height:600, iframe:true, transition:'none'}); +}) diff --git a/trunk/module/testcase/js/create.js b/trunk/module/testcase/js/create.js new file mode 100644 index 0000000000..7a628a1f0a --- /dev/null +++ b/trunk/module/testcase/js/create.js @@ -0,0 +1,19 @@ +/* Set the story priview link. */ +function setPreview() +{ + if(!$('#story').val()) + { + $('#preview').addClass('hidden'); + } + else + { + storyLink = createLink('story', 'view', "storyID=" + $('#story').val()); + $('#preview').removeClass('hidden'); + $('#preview').attr('href', storyLink); + } +} +$(function() +{ + $("#story").chosen({no_results_text: noResultsMatch}); + $("#preview").colorbox({width:1000, height:600, iframe:true, transition:'elastic', speed:350, scrolling:true}); +}) diff --git a/trunk/module/testcase/js/edit.js b/trunk/module/testcase/js/edit.js new file mode 100644 index 0000000000..5e1c0dd109 --- /dev/null +++ b/trunk/module/testcase/js/edit.js @@ -0,0 +1,20 @@ +/** + * Get story list. + * + * @param string $module + * @access public + * @return void + */ +function getList() +{ + productID = $('#product').get(0).value; + storyID = $('#story').get(0).value; + link = createLink('search', 'select', 'productID=' + productID + '&projectID=0&module=story&moduleID=' + storyID); + $('#storyListIdBox a').attr("href", link); +} + +$(document).ready(function() +{ + $("#story").chosen({no_results_text: noResultsMatch}); + $("#searchStories").colorbox({width:680, height:400, iframe:true, transition:'none'}); +}); diff --git a/trunk/module/testcase/js/view.js b/trunk/module/testcase/js/view.js new file mode 100755 index 0000000000..039ba61bd9 --- /dev/null +++ b/trunk/module/testcase/js/view.js @@ -0,0 +1,9 @@ +$(document).ready(function() +{ + $(".runCase").colorbox({width:900, height:600, iframe:true, transition:'none'}); + $(".results").colorbox({width:900, height:600, iframe:true, transition:'none'}); +}) +function setComment() +{ + $('#comment').toggle(); +} diff --git a/trunk/module/testcase/lang/en.php b/trunk/module/testcase/lang/en.php new file mode 100644 index 0000000000..532dde5735 --- /dev/null +++ b/trunk/module/testcase/lang/en.php @@ -0,0 +1,145 @@ + + * @package testcase + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->testcase->id = 'ID'; +$lang->testcase->product = 'Product'; +$lang->testcase->module = 'Module'; +$lang->testcase->story = 'Story'; +$lang->testcase->storyVersion = 'Story version'; +$lang->testcase->title = 'Title'; +$lang->testcase->precondition = 'precondition'; +$lang->testcase->pri = 'Priority'; +$lang->testcase->type = 'Type'; +$lang->testcase->status = 'Status'; +$lang->testcase->steps = 'Steps'; +$lang->testcase->frequency = 'Frequency'; +$lang->testcase->order = 'Order'; +$lang->testcase->openedBy = 'Opened by '; +$lang->testcase->openedDate = 'Opened date'; +$lang->testcase->lastEditedBy = 'Last edited by'; +$lang->testcase->lastEditedDate = 'Last edited date'; +$lang->testcase->version = 'Version'; +$lang->testcase->result = 'Result'; +$lang->testcase->real = 'Real'; +$lang->testcase->keywords = 'Keywords'; +$lang->testcase->files = 'Files'; +$lang->testcase->howRun = 'How run'; +$lang->testcase->scriptedBy = 'Scripted by'; +$lang->testcase->scriptedDate = 'Scripted date'; +$lang->testcase->scriptedStatus = 'Scripted status'; +$lang->testcase->scriptedLocation = 'Script location'; +$lang->testcase->linkCase = 'Related cases'; +$lang->testcase->stage = 'Stage'; +$lang->testcase->lastEditedByAB = 'Last edited by'; +$lang->testcase->lastEditedDateAB = 'Last edited date'; +$lang->testcase->allProduct = 'All product'; +$lang->case = $lang->testcase; // For dao checking using. Because 'case' is a php keywords, so the module name is testcase, table name is still case. + +$lang->testcase->stepID = 'ID'; +$lang->testcase->stepDesc = 'Step'; +$lang->testcase->stepExpect = 'Expect'; + +$lang->testcase->common = 'Case'; +$lang->testcase->index = "Index"; +$lang->testcase->create = "Create"; +$lang->testcase->batchCreate = "Batch create"; +$lang->testcase->delete = "Delete"; +$lang->testcase->view = "Info"; +$lang->testcase->edit = "Edit"; +$lang->testcase->delete = "Delete"; +$lang->testcase->browse = "Browse"; +$lang->testcase->import = "Import"; +$lang->testcase->export = "Export"; +$lang->testcase->confirmStoryChange = 'Confirm story change'; + +$lang->testcase->deleteStep = 'Delete'; +$lang->testcase->insertBefore = 'Insert before'; +$lang->testcase->insertAfter = 'Insert after'; + +$lang->testcase->selectProduct = 'Select product'; +$lang->testcase->byModule = 'By module'; +$lang->testcase->assignToMe = 'Cases to me'; +$lang->testcase->openedByMe = 'My Opened cases'; +$lang->testcase->allCases = 'All case'; +$lang->testcase->needConfirm = 'Story changed'; +$lang->testcase->moduleCases = 'By module'; +$lang->testcase->bySearch = 'By search'; +$lang->testcase->doneByMe = 'My runed cases'; + +$lang->testcase->lblProductAndModule = 'Product & module'; +$lang->testcase->lblTypeAndPri = 'Type & priority'; +$lang->testcase->lblSystemBrowserAndHardware = 'OS & browser'; +$lang->testcase->lblAssignAndMail = 'Assigned & mailto'; +$lang->testcase->lblStory = 'Story'; +$lang->testcase->lblLastEdited = 'Last edited'; + +$lang->testcase->legendRelated = 'Related info'; +$lang->testcase->legendBasicInfo = 'Basic info'; +$lang->testcase->legendMailto = 'Mailto'; +$lang->testcase->legendAttatch = 'Files'; +$lang->testcase->legendLinkBugs = 'Bug'; +$lang->testcase->legendOpenAndEdit = 'Open & edit'; +$lang->testcase->legendStoryAndTask = 'Story'; +$lang->testcase->legendCases = 'Related cases'; +$lang->testcase->legendSteps = 'Steps'; +$lang->testcase->legendAction = 'Action'; +$lang->testcase->legendHistory = 'History'; +$lang->testcase->legendComment = 'Comment'; +$lang->testcase->legendProduct = 'Product & module'; +$lang->testcase->legendVersion = 'Versions'; + +$lang->testcase->confirmDelete = 'Are you sure to delete this case?'; +$lang->testcase->same = 'The same as above'; +$lang->testcase->notes = '(Notes: the type and title must be written, otherwise it is no use)'; + +$lang->testcase->priList[3] = 3; +$lang->testcase->priList[1] = 1; +$lang->testcase->priList[2] = 2; +$lang->testcase->priList[4] = 4; + +/* Define the types. */ +$lang->testcase->typeList[''] = ''; +$lang->testcase->typeList['feature'] = 'Feature'; +$lang->testcase->typeList['performance'] = 'Performance'; +$lang->testcase->typeList['config'] = 'Config'; +$lang->testcase->typeList['install'] = 'Install'; +$lang->testcase->typeList['security'] = 'Security'; +$lang->testcase->typeList['other'] = 'Other'; + +$lang->testcase->stageList[''] = ''; +$lang->testcase->stageList['unittest'] = 'Unit testing'; +$lang->testcase->stageList['feature'] = 'Feature testing'; +$lang->testcase->stageList['intergrate'] = 'Integrate testing'; +$lang->testcase->stageList['system'] = 'System testing'; +$lang->testcase->stageList['smoke'] = 'Smoking testing'; +$lang->testcase->stageList['bvt'] = 'BVT testing'; + +$lang->testcase->stageListAB['unittest'] = 'Unit'; +$lang->testcase->stageListAB['feature'] = 'Feature'; +$lang->testcase->stageListAB['intergrate'] = 'Integrate'; +$lang->testcase->stageListAB['system'] = 'System'; +$lang->testcase->stageListAB['smoke'] = 'Smoking'; +$lang->testcase->stageListAB['bvt'] = 'BVT'; + +$lang->testcase->statusList[''] = ''; +$lang->testcase->statusList['normal'] = 'Normal'; +$lang->testcase->statusList['blocked'] = 'Blocked'; +$lang->testcase->statusList['investigate'] = 'Investigate'; + +$lang->testcase->resultList['n/a'] = 'N/A'; +$lang->testcase->resultList['pass'] = 'Pass'; +$lang->testcase->resultList['fail'] = 'Fail'; +$lang->testcase->resultList['blocked'] = 'Blocked'; + +$lang->testcase->buttonEdit = 'Edit'; +$lang->testcase->buttonToList = 'Back'; + +$lang->testcase->placeholder->keywords = 'keywords'; diff --git a/trunk/module/testcase/lang/zh-cn.php b/trunk/module/testcase/lang/zh-cn.php new file mode 100644 index 0000000000..b907e01f4e --- /dev/null +++ b/trunk/module/testcase/lang/zh-cn.php @@ -0,0 +1,143 @@ + + * @package testcase + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->testcase->id = '用例编号'; +$lang->testcase->product = '所属产品'; +$lang->testcase->module = '所属模块'; +$lang->testcase->story = '相关需求'; +$lang->testcase->storyVersion = '需求版本'; +$lang->testcase->title = '用例标题'; +$lang->testcase->precondition = '前置条件'; +$lang->testcase->pri = '优先级'; +$lang->testcase->type = '用例类型'; +$lang->testcase->status = '用例状态'; +$lang->testcase->steps = '用例步骤'; +$lang->testcase->frequency = '执行频率'; +$lang->testcase->order = '排序'; +$lang->testcase->openedBy = '由谁创建 '; +$lang->testcase->openedDate = '创建日期'; +$lang->testcase->lastEditedBy = '最后修改者'; +$lang->testcase->lastEditedDate = '最后修改日期'; +$lang->testcase->version = '用例版本'; +$lang->testcase->result = '测试结果'; +$lang->testcase->real = '实际情况'; +$lang->testcase->keywords = '关键词'; +$lang->testcase->files = '附件'; +$lang->testcase->howRun = '执行方式'; +$lang->testcase->scriptedBy = '由谁编写'; +$lang->testcase->scriptedDate = '编写日期'; +$lang->testcase->scriptedStatus = '脚本状态'; +$lang->testcase->scriptedLocation = '脚本位置'; +$lang->testcase->linkCase = '相关用例'; +$lang->testcase->stage = '适用阶段'; +$lang->testcase->lastEditedByAB = '修改者'; +$lang->testcase->lastEditedDateAB = '修改日期'; +$lang->testcase->allProduct = '所有产品'; +$lang->case = $lang->testcase; // 用于DAO检查时使用。因为case是系统关键字,所以无法定义该模块为case,只能使用testcase,但表还是使用的case。 + +$lang->testcase->stepID = '编号'; +$lang->testcase->stepDesc = '步骤'; +$lang->testcase->stepExpect = '预期'; + +$lang->testcase->common = '用例管理'; +$lang->testcase->index = "用例管理首页"; +$lang->testcase->create = "建用例"; +$lang->testcase->batchCreate = "批量添加"; +$lang->testcase->delete = "删除用例"; +$lang->testcase->view = "用例详情"; +$lang->testcase->edit = "编辑"; +$lang->testcase->delete = "删除"; +$lang->testcase->browse = "用例列表"; +$lang->testcase->import = "导入用例"; +$lang->testcase->export = "导出"; +$lang->testcase->confirmStoryChange = '确认需求变动'; + +$lang->testcase->deleteStep = '删除'; +$lang->testcase->insertBefore = '之前添加'; +$lang->testcase->insertAfter = '之后添加'; + +$lang->testcase->selectProduct = '请选择产品'; +$lang->testcase->byModule = '按模块'; +$lang->testcase->assignToMe = '指派给我的用例'; +$lang->testcase->openedByMe = '由我创建的用例'; +$lang->testcase->allCases = '所有'; +$lang->testcase->needConfirm = '需求变动'; +$lang->testcase->moduleCases = '按模块'; +$lang->testcase->bySearch = '搜索'; +$lang->testcase->doneByMe = '我完成的用例'; + +$lang->testcase->lblProductAndModule = '产品模块'; +$lang->testcase->lblTypeAndPri = '类型&优先级'; +$lang->testcase->lblSystemBrowserAndHardware = '系统::浏览器'; +$lang->testcase->lblAssignAndMail = '指派给::抄送给'; +$lang->testcase->lblStory = '相关需求'; +$lang->testcase->lblLastEdited = '最后编辑'; + +$lang->testcase->legendRelated = '相关信息'; +$lang->testcase->legendBasicInfo = '基本信息'; +$lang->testcase->legendMailto = '抄送给'; +$lang->testcase->legendAttatch = '附件'; +$lang->testcase->legendLinkBugs = '相关Bug'; +$lang->testcase->legendOpenAndEdit = '创建编辑'; +$lang->testcase->legendStoryAndTask = '需求::任务'; +$lang->testcase->legendCases = '相关用例'; +$lang->testcase->legendSteps = '用例步骤'; +$lang->testcase->legendAction = '操作'; +$lang->testcase->legendHistory = '历史记录'; +$lang->testcase->legendComment = '备注'; +$lang->testcase->legendProduct = '产品模块'; +$lang->testcase->legendVersion = '版本历史'; + +$lang->testcase->confirmDelete = '您确认要删除该测试用例吗?'; +$lang->testcase->same = '同上'; +$lang->testcase->notes = '(注:“用例类型”和“用例标题”必须填写,否则此行无效)'; + +$lang->testcase->priList[3] = 3; +$lang->testcase->priList[1] = 1; +$lang->testcase->priList[2] = 2; +$lang->testcase->priList[4] = 4; + +/* Define the types. */ +$lang->testcase->typeList[''] = ''; +$lang->testcase->typeList['feature'] = '功能测试'; +$lang->testcase->typeList['performance'] = '性能测试'; +$lang->testcase->typeList['config'] = '配置相关'; +$lang->testcase->typeList['install'] = '安装部署'; +$lang->testcase->typeList['security'] = '安全相关'; +$lang->testcase->typeList['other'] = '其他'; + +$lang->testcase->stageList[''] = ''; +$lang->testcase->stageList['unittest'] = '单元测试阶段'; +$lang->testcase->stageList['feature'] = '功能测试阶段'; +$lang->testcase->stageList['intergrate'] = '集成测试阶段'; +$lang->testcase->stageList['system'] = '系统测试阶段'; +$lang->testcase->stageList['smoke'] = '冒烟测试阶段'; +$lang->testcase->stageList['bvt'] = '版本验证阶段'; + +$lang->testcase->stageListAB['unittest'] = '单元'; +$lang->testcase->stageListAB['feature'] = '功能'; +$lang->testcase->stageListAB['intergrate'] = '集成'; +$lang->testcase->stageListAB['system'] = '系统'; +$lang->testcase->stageListAB['smoke'] = '冒烟'; +$lang->testcase->stageListAB['bvt'] = '版本'; + +$lang->testcase->statusList[''] = ''; +$lang->testcase->statusList['normal'] = '正常'; +$lang->testcase->statusList['blocked'] = '被阻塞'; +$lang->testcase->statusList['investigate'] = '研究中'; + +$lang->testcase->resultList['n/a'] = 'N/A'; +$lang->testcase->resultList['pass'] = '通过'; +$lang->testcase->resultList['fail'] = '失败'; +$lang->testcase->resultList['blocked'] = '阻塞'; + +$lang->testcase->buttonEdit = '编辑'; +$lang->testcase->buttonToList = '返回'; diff --git a/trunk/module/testcase/lang/zh-tw.php b/trunk/module/testcase/lang/zh-tw.php new file mode 100644 index 0000000000..1d96e85b51 --- /dev/null +++ b/trunk/module/testcase/lang/zh-tw.php @@ -0,0 +1,143 @@ + + * @package testcase + * @version $Id: zh-tw.php 3431 2012-08-30 07:45:20Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +$lang->testcase->id = '用例編號'; +$lang->testcase->product = '所屬產品'; +$lang->testcase->module = '所屬模組'; +$lang->testcase->story = '相關需求'; +$lang->testcase->storyVersion = '需求版本'; +$lang->testcase->title = '用例標題'; +$lang->testcase->precondition = '前置條件'; +$lang->testcase->pri = '優先順序'; +$lang->testcase->type = '用例類型'; +$lang->testcase->status = '用例狀態'; +$lang->testcase->steps = '用例步驟'; +$lang->testcase->frequency = '執行頻率'; +$lang->testcase->order = '排序'; +$lang->testcase->openedBy = '由誰創建 '; +$lang->testcase->openedDate = '創建日期'; +$lang->testcase->lastEditedBy = '最後修改者'; +$lang->testcase->lastEditedDate = '最後修改日期'; +$lang->testcase->version = '用例版本'; +$lang->testcase->result = '測試結果'; +$lang->testcase->real = '實際情況'; +$lang->testcase->keywords = '關鍵詞'; +$lang->testcase->files = '附件'; +$lang->testcase->howRun = '執行方式'; +$lang->testcase->scriptedBy = '由誰編寫'; +$lang->testcase->scriptedDate = '編寫日期'; +$lang->testcase->scriptedStatus = '腳本狀態'; +$lang->testcase->scriptedLocation = '腳本位置'; +$lang->testcase->linkCase = '相關用例'; +$lang->testcase->stage = '適用階段'; +$lang->testcase->lastEditedByAB = '修改者'; +$lang->testcase->lastEditedDateAB = '修改日期'; +$lang->testcase->allProduct = '所有產品'; +$lang->case = $lang->testcase; // 用於DAO檢查時使用。因為case是系統關鍵字,所以無法定義該模組為case,只能使用testcase,但表還是使用的case。 + +$lang->testcase->stepID = '編號'; +$lang->testcase->stepDesc = '步驟'; +$lang->testcase->stepExpect = '預期'; + +$lang->testcase->common = '用例管理'; +$lang->testcase->index = "用例管理首頁"; +$lang->testcase->create = "建用例"; +$lang->testcase->batchCreate = "批量添加"; +$lang->testcase->delete = "刪除用例"; +$lang->testcase->view = "用例詳情"; +$lang->testcase->edit = "編輯"; +$lang->testcase->delete = "刪除"; +$lang->testcase->browse = "用例列表"; +$lang->testcase->import = "導入用例"; +$lang->testcase->export = "導出"; +$lang->testcase->confirmStoryChange = '確認需求變動'; + +$lang->testcase->deleteStep = '刪除'; +$lang->testcase->insertBefore = '之前添加'; +$lang->testcase->insertAfter = '之後添加'; + +$lang->testcase->selectProduct = '請選擇產品'; +$lang->testcase->byModule = '按模組'; +$lang->testcase->assignToMe = '指派給我的用例'; +$lang->testcase->openedByMe = '由我創建的用例'; +$lang->testcase->allCases = '所有'; +$lang->testcase->needConfirm = '需求變動'; +$lang->testcase->moduleCases = '按模組'; +$lang->testcase->bySearch = '搜索'; +$lang->testcase->doneByMe = '我完成的用例'; + +$lang->testcase->lblProductAndModule = '產品模組'; +$lang->testcase->lblTypeAndPri = '類型&優先順序'; +$lang->testcase->lblSystemBrowserAndHardware = '系統::瀏覽器'; +$lang->testcase->lblAssignAndMail = '指派給::抄送給'; +$lang->testcase->lblStory = '相關需求'; +$lang->testcase->lblLastEdited = '最後編輯'; + +$lang->testcase->legendRelated = '相關信息'; +$lang->testcase->legendBasicInfo = '基本信息'; +$lang->testcase->legendMailto = '抄送給'; +$lang->testcase->legendAttatch = '附件'; +$lang->testcase->legendLinkBugs = '相關Bug'; +$lang->testcase->legendOpenAndEdit = '創建編輯'; +$lang->testcase->legendStoryAndTask = '需求::任務'; +$lang->testcase->legendCases = '相關用例'; +$lang->testcase->legendSteps = '用例步驟'; +$lang->testcase->legendAction = '操作'; +$lang->testcase->legendHistory = '歷史記錄'; +$lang->testcase->legendComment = '備註'; +$lang->testcase->legendProduct = '產品模組'; +$lang->testcase->legendVersion = '版本歷史'; + +$lang->testcase->confirmDelete = '您確認要刪除該測試用例嗎?'; +$lang->testcase->same = '同上'; +$lang->testcase->notes = '(註:“用例類型”和“用例標題”必須填寫,否則此行無效)'; + +$lang->testcase->priList[3] = 3; +$lang->testcase->priList[1] = 1; +$lang->testcase->priList[2] = 2; +$lang->testcase->priList[4] = 4; + +/* Define the types. */ +$lang->testcase->typeList[''] = ''; +$lang->testcase->typeList['feature'] = '功能測試'; +$lang->testcase->typeList['performance'] = '性能測試'; +$lang->testcase->typeList['config'] = '配置相關'; +$lang->testcase->typeList['install'] = '安裝部署'; +$lang->testcase->typeList['security'] = '安全相關'; +$lang->testcase->typeList['other'] = '其他'; + +$lang->testcase->stageList[''] = ''; +$lang->testcase->stageList['unittest'] = '單元測試階段'; +$lang->testcase->stageList['feature'] = '功能測試階段'; +$lang->testcase->stageList['intergrate'] = '整合測試階段'; +$lang->testcase->stageList['system'] = '系統測試階段'; +$lang->testcase->stageList['smoke'] = '冒煙測試階段'; +$lang->testcase->stageList['bvt'] = '版本驗證階段'; + +$lang->testcase->stageListAB['unittest'] = '單元'; +$lang->testcase->stageListAB['feature'] = '功能'; +$lang->testcase->stageListAB['intergrate'] = '整合'; +$lang->testcase->stageListAB['system'] = '系統'; +$lang->testcase->stageListAB['smoke'] = '冒煙'; +$lang->testcase->stageListAB['bvt'] = '版本'; + +$lang->testcase->statusList[''] = ''; +$lang->testcase->statusList['normal'] = '正常'; +$lang->testcase->statusList['blocked'] = '被阻塞'; +$lang->testcase->statusList['investigate'] = '研究中'; + +$lang->testcase->resultList['n/a'] = 'N/A'; +$lang->testcase->resultList['pass'] = '通過'; +$lang->testcase->resultList['fail'] = '失敗'; +$lang->testcase->resultList['blocked'] = '阻塞'; + +$lang->testcase->buttonEdit = '編輯'; +$lang->testcase->buttonToList = '返回'; diff --git a/trunk/module/testcase/model.php b/trunk/module/testcase/model.php new file mode 100644 index 0000000000..afb92c2652 --- /dev/null +++ b/trunk/module/testcase/model.php @@ -0,0 +1,318 @@ + + * @package case + * @version $Id$ + * @link http://www.zentao.net + */ +?> +loadModel('product')->setMenu($products, $productID); + $selectHtml = $this->product->select($products, $productID, 'testcase', 'browse'); + foreach($this->lang->testcase->menu as $key => $menu) + { + $replace = ($key == 'product') ? $selectHtml : $productID; + common::setMenuVars($this->lang->testcase->menu, $key, $replace); + } + } + + /** + * Create a case. + * + * @param int $bugID + * @access public + * @return void + */ + function create($bugID) + { + $now = helper::now(); + $case = fixer::input('post') + ->add('openedBy', $this->app->user->account) + ->add('openedDate', $now) + ->add('status', 'normal') + ->add('version', 1) + ->add('fromBug', $bugID) + ->setIF($this->post->story != false, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story)) + ->remove('steps,expects,files,labels') + ->setDefault('story', 0) + ->specialChars('title') + ->join('stage', ',') + ->get(); + $this->dao->insert(TABLE_CASE)->data($case)->autoCheck()->batchCheck($this->config->testcase->create->requiredFields, 'notempty')->exec(); + if(!$this->dao->isError()) + { + $caseID = $this->dao->lastInsertID(); + $this->loadModel('file')->saveUpload('testcase', $caseID); + foreach($this->post->steps as $stepID => $stepDesc) + { + if(empty($stepDesc)) continue; + $step->case = $caseID; + $step->version = 1; + $step->desc = htmlspecialchars($stepDesc); + $step->expect = htmlspecialchars($this->post->expects[$stepID]); + $this->dao->insert(TABLE_CASESTEP)->data($step)->autoCheck()->exec(); + } + return $caseID; + } + } + + /** + * Create a batch case. + * + * @access public + * @return void + */ + function batchCreate($productID) + { + $now = helper::now(); + $cases = fixer::input('post')->get(); + for($i = 0; $i < $this->config->testcase->batchCreate; $i++) + { + if($cases->type[$i] != '' and $cases->title[$i] != '') + { + $data[$i]->product = $productID; + $data[$i]->module = $cases->module[$i] == 'same' ? ($i == 0 ? 0 : $data[$i-1]->module) : $cases->module[$i]; + $data[$i]->type = $cases->type[$i] == 'same' ? ($i == 0 ? '' : $data[$i-1]->type) : $cases->type[$i]; + $data[$i]->story = $cases->story[$i] == 'same' ? ($i == 0 ? 0 : $data[$i-1]->story) : $cases->story[$i]; + $data[$i]->title = $cases->title[$i]; + $data[$i]->openedBy = $this->app->user->account; + $data[$i]->openedDate = $now; + $data[$i]->status = 'normal'; + $data[$i]->version = 1; + if($data[$i]->story != 0) $data[$i]->storyVersion = $this->loadModel('story')->getVersion($this->post->story); + + $this->dao->insert(TABLE_CASE)->data($data[$i]) + ->autoCheck() + ->batchCheck($this->config->testcase->create->requiredFields, 'notempty') + ->exec(); + + if(dao::isError()) + { + echo js::error(dao::getError()); + die(js::reload('parent')); + } + + $caseID = $this->dao->lastInsertID(); + $actionID = $this->loadModel('action')->create('case', $caseID, 'Opened'); + } + else + { + unset($cases->module[$i]); + unset($cases->type[$i]); + unset($cases->story[$i]); + unset($cases->title[$i]); + } + } + } + + /** + * Get cases of a module. + * + * @param int $productID + * @param int $moduleIds + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getModuleCases($productID, $moduleIds = 0, $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('*')->from(TABLE_CASE) + ->where('product')->eq((int)$productID) + ->beginIF($moduleIds)->andWhere('module')->in($moduleIds)->fi() + ->andWhere('deleted')->eq('0') + ->orderBy($orderBy)->page($pager)->fetchAll(); + } + + /** + * Get case info by ID. + * + * @param int $caseID + * @param int $version + * @access public + * @return object|bool + */ + public function getById($caseID, $version = 0) + { + $case = $this->dao->findById($caseID)->from(TABLE_CASE)->fetch(); + if(!$case) return false; + foreach($case as $key => $value) if(strpos($key, 'Date') !== false and !(int)substr($value, 0, 4)) $case->$key = ''; + if($case->story) + { + $story = $this->dao->findById($case->story)->from(TABLE_STORY)->fields('title, status, version')->fetch(); + $case->storyTitle = $story->title; + $case->storyStatus = $story->status; + $case->latestStoryVersion = $story->version; + } + if($case->linkCase) $case->linkCaseTitles = $this->dao->select('id,title')->from(TABLE_CASE)->where('id')->in($case->linkCase)->fetchPairs(); + if($version == 0) $version = $case->version; + $case->steps = $this->dao->select('*')->from(TABLE_CASESTEP)->where('`case`')->eq($caseID)->andWhere('version')->eq($version)->fetchAll(); + $case->files = $this->loadModel('file')->getByObject('testcase', $caseID); + $case->currentVersion = $version ? $version : $case->version; + return $case; + } + + /** + * Update a case. + * + * @param int $caseID + * @access public + * @return void + */ + public function update($caseID) + { + $oldCase = $this->getById($caseID); + $now = helper::now(); + $stepChanged = false; + $steps = array(); + + //---------------- Judge steps changed or not.-------------------- */ + + /* Remove the empty setps in post. */ + foreach($this->post->steps as $key => $desc) + { + $desc = trim($desc); + if(!empty($desc)) $steps[] = array('desc' => $desc, 'expect' => trim($this->post->expects[$key])); + } + + /* If step count changed, case changed. */ + if(count($oldCase->steps) != count($steps)) + { + $stepChanged = true; + } + else + { + /* Compare every step. */ + foreach($oldCase->steps as $key => $oldStep) + { + if(trim($oldStep->desc) != trim($steps[$key]['desc']) or trim($oldStep->expect) != $steps[$key]['expect']) + { + $stepChanged = true; + break; + } + } + } + $version = $stepChanged ? $oldCase->version + 1 : $oldCase->version; + + $case = fixer::input('post') + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->add('version', $version) + ->setIF($this->post->story != false and $this->post->story != $oldCase->story, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story)) + ->setDefault('story', 0) + ->specialChars('title') + ->join('stage', ',') + ->remove('comment,steps,expects,files,labels') + ->get(); + $this->dao->update(TABLE_CASE)->data($case)->autoCheck()->batchCheck($this->config->testcase->edit->requiredFields, 'notempty')->where('id')->eq((int)$caseID)->exec(); + if(!$this->dao->isError()) + { + if($stepChanged) + { + foreach($this->post->steps as $stepID => $stepDesc) + { + if(empty($stepDesc)) continue; + $step->case = $caseID; + $step->version = $version; + $step->desc = htmlspecialchars($stepDesc); + $step->expect = htmlspecialchars($this->post->expects[$stepID]); + $this->dao->insert(TABLE_CASESTEP)->data($step)->autoCheck()->exec(); + } + } + + /* Join the steps to diff. */ + if($stepChanged) + { + $oldCase->steps = $this->joinStep($oldCase->steps); + $case->steps = $this->joinStep($this->getById($caseID, $version)->steps); + } + else + { + unset($oldCase->steps); + } + return common::createChanges($oldCase, $case); + } + } + + /** + * Join steps to a string, thus can diff them. + * + * @param array $steps + * @access public + * @return string + */ + public function joinStep($steps) + { + $retrun = ''; + foreach($steps as $step) $return .= $step->desc . ' EXPECT:' . $step->expect . "\n"; + return $return; + } + + /** + * Create case steps from a bug's step. + * + * @param string $steps + * @access public + * @return array + */ + function createStepsFromBug($steps) + { + $steps = strip_tags($steps); + $caseSteps = array((object)array('desc' => $steps, 'expect' => '')); // the default steps before parse. + $lblStep = strip_tags($this->lang->bug->tplStep); + $lblResult = strip_tags($this->lang->bug->tplResult); + $lblExpect = strip_tags($this->lang->bug->tplExpect); + $lblStepPos = strpos($steps, $lblStep); + $lblResultPos = strpos($steps, $lblResult); + $lblExpectPos = strpos($steps, $lblExpect); + + if($lblStepPos === false or $lblResultPos === false or $lblExpectPos === false) return $caseSteps; + + $caseSteps = substr($steps, $lblStepPos + strlen($lblStep), $lblResultPos - strlen($lblStep)); + $caseExpect = substr($steps, $lblExpectPos + strlen($lblExpect)); + $caseSteps = trim($caseSteps); + $caseExpect = trim($caseExpect); + + $caseSteps = explode("\n", trim($caseSteps)); + $stepCount = count($caseSteps); + foreach($caseSteps as $key => $caseStep) + { + $expect = $key + 1 == $stepCount ? $caseExpect : ''; + $caseSteps[$key] = (object)array('desc' => trim($caseStep), 'expect' => $expect); + } + return $caseSteps; + } + + /** + * Adjust the action is clickable. + * + * @param object $case + * @param string $action + * @access public + * @return void + */ + public function isClickable($case, $action) + { + $action = strtolower($action); + + if($action == 'createbug') return $case->lastRunResult == 'fail'; + + return true; + } +} diff --git a/trunk/module/testcase/view/batchcreate.html.php b/trunk/module/testcase/view/batchcreate.html.php new file mode 100644 index 0000000000..19e0494754 --- /dev/null +++ b/trunk/module/testcase/view/batchcreate.html.php @@ -0,0 +1,47 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + testcase->batchCreate; $i++):?> + testcase->same; if($i != 0) $currentModuleID = 'same';?> + testcase->typeList['same'] = $lang->testcase->same; $type = ($i == 0 ? 'feature' : 'same');?> + testcase->same; $story = $i == 0 ? '' : 'same';?> + + + + + + + + + + + + +
      testcase->batchCreate;?>
      idAB;?>testcase->module;?>testcase->type;?>testcase->story;?>testcase->title;?>
      testcase->typeList, $type, "class=select-1"); echo "*";?>*";?>
      +
      testcase->notes;?>
      +
      +
      +
      + diff --git a/trunk/module/testcase/view/browse.html.php b/trunk/module/testcase/view/browse.html.php new file mode 100644 index 0000000000..124622b69e --- /dev/null +++ b/trunk/module/testcase/view/browse.html.php @@ -0,0 +1,110 @@ + + * @package testcase + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + +
      +
      + " . $lang->testcase->moduleCases . " "; + echo "" . html::a($this->createLink('testcase', 'browse', "productid=$productID&browseType=all¶m=0&orderBy=$orderBy&recTotal=0&recPerPage=200"), $lang->testcase->allCases) . ""; + echo "" . html::a($this->createLink('testcase', 'browse', "productid=$productID&browseType=needconfirm¶m=0"), $lang->testcase->needConfirm) . ""; + echo "{$lang->testcase->bySearch} "; + ?> +
      +
      + testcase->import}'"); ?> + + + +
      +
      +
      '>
      +
      '> + + + + + + +
      +
      +
      + +
      + tree->manage);?> + tree->fix, 'hiddenwin');?> +
      +
      +
      + recTotal}&recPerPage={$pager->recPerPage}"; ?> + + + + + + + + + + + + + + + + + + + + + + id");?> + + + + + + + + + + + + + + + + + + + +
      idAB);?>priAB);?>testcase->title);?>testcase->story);?>actions;?> typeAB);?> openedByAB);?> testtask->lastRunAccount);?> testtask->lastRunTime);?> testtask->lastRunResult);?>statusAB);?>actions;?>
      id));?>pri?>title);?>createLink('story', 'view', "storyID=$case->story"), $case->storyTitle, '_blank');?>id"), $lang->confirm, 'hiddenwin');?>testcase->typeList[$case->type];?>openedBy];?>lastRunner];?>lastRunDate)) echo date(DT_MONTHTIME1, strtotime($case->lastRunDate));?>lastRunResult) echo $lang->testcase->resultList[$case->lastRunResult];?>testcase->statusList[$case->status];?> + id&version=$case->version", $this->app->loadLang('testtask')->testtask->runCase, '', 'class="runcase"'); + common::printLink('testtask', 'results', "runID=0&caseID=$case->id", $lang->testtask->results, '', 'class="results"'); + common::printIcon('testcase', 'edit', "caseID=$case->id", $case, 'list'); + common::printIcon('testcase', 'create', "productID=$case->product&moduleID=$case->module&from=testcase¶m=$case->id", $case, 'list', 'copy'); + common::printIcon('testcase', 'delete', "caseID=$case->id", '', 'list', '', 'hiddenwin'); + common::printIcon('testcase', 'createBug', "product=$case->product&extra=caseID=$case->id,version=$case->version,runID=", $case, 'list', 'createBug'); + ?> +
      show();?>
      +
      +
      + diff --git a/trunk/module/testcase/view/create.html.php b/trunk/module/testcase/view/create.html.php new file mode 100644 index 0000000000..fb1bc00af5 --- /dev/null +++ b/trunk/module/testcase/view/create.html.php @@ -0,0 +1,98 @@ + + * @package case + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      testcase->create;?>
      testcase->lblProductAndModule;?> + + +
      testcase->type;?>testcase->typeList, $type, 'class=select-3');?>
      testcase->stage;?>testcase->stageList, $stage, "class='select-3' multiple='multiple'");?>
      testcase->pri;?>testcase->priList, $pri, 'class=select-3');?>
      testcase->lblStory;?> +
      + + + + createLink('story', 'view', "storyID=$storyID"), $lang->preview, '', "class='iframe' id='preview'");?> + +
      testcase->title;?>
      testcase->precondition;?>
      testcase->steps;?> + + + + + + + + $step) + { + $stepID += 1; + echo ""; + echo ""; + echo ''; + echo ''; + echo ""; + echo ''; + } + ?> +
      testcase->stepID;?>testcase->stepDesc;?>testcase->stepExpect;?>actions;?>
      $stepID' . html::textarea('steps[]', $step->desc, "rows='3' class='w-p100'") . '' . html::textarea('expects[]', $step->expect, "rows='3' class='w-p100'") . '"; + echo "
      "; + echo "
      "; + echo "
      "; + echo "
      +
      testcase->keywords;?>
      testcase->files;?>fetch('file', 'buildform');?>
      +
      + diff --git a/trunk/module/testcase/view/edit.html.php b/trunk/module/testcase/view/edit.html.php new file mode 100644 index 0000000000..49c728b86b --- /dev/null +++ b/trunk/module/testcase/view/edit.html.php @@ -0,0 +1,132 @@ + + * @package case + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      +
      +
      + CASE #id . $lang->colon;?> + title, 'class=text-1');?> +
      +
      +
      + + + + + + + +
      +
      + testcase->precondition;?> + precondition, "rows='4' class='w-p100'");?> +
      + + + + + + + + steps as $stepID => $step) + { + $stepID += 1; + echo ""; + echo ""; + echo ''; + echo ''; + echo ""; + echo ''; + } + ?> +
      testcase->stepID;?>testcase->stepDesc;?>testcase->stepExpect;?>actions;?>
      $stepID' . html::textarea('steps[]', $step->desc, "rows='3' class='w-p100'") . '' . html::textarea('expects[]', $step->expect, "rows='3' class='w-p100'") . '"; + echo "
      "; + echo "
      "; + echo "
      "; + echo "
      + +
      + testcase->legendComment;?> + +
      +
      + testcase->legendAttatch;?> + fetch('file', 'buildform', 'filecount=2');?> +
      + +
      + + "' /> +
      + + +
      +
      + testcase->legendBasicInfo;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      testcase->product;?>
      testcase->module;?>
      testcase->story;?>
      story, 'class=select-1');?>
      +
      testcase->type;?>testcase->typeList, $case->type, 'class=select-1');?> +
      testcase->stage;?>testcase->stageList, $case->stage, "class='select-1' multiple='multiple'");?>
      testcase->pri;?>testcase->priList, $case->pri, 'class=select-1');?> +
      testcase->status;?>testcase->statusList, $case->status, 'class=select-1');?>
      testcase->keywords;?>keywords, 'class=text-1');?>
      testcase->linkCase;?>linkCase, 'class=text-1');?>
      +
      +
      + testcase->legendOpenAndEdit;?> + + + + + + + + +
      testcase->openedBy;?>openedBy . $lang->at . $case->openedDate;?>
      testcase->lblLastEdited;?>lastEditedBy) echo $case->lastEditedBy . $lang->at . $case->lastEditedDate;?>
      +
      +
      + diff --git a/trunk/module/testcase/view/export.html.php b/trunk/module/testcase/view/export.html.php new file mode 100755 index 0000000000..a80d0f7dc8 --- /dev/null +++ b/trunk/module/testcase/view/export.html.php @@ -0,0 +1,13 @@ + + * @package testcase + * @version $Id$ + * @link http://www.zentao.net + */ +?> + diff --git a/trunk/module/testcase/view/header.html.php b/trunk/module/testcase/view/header.html.php new file mode 100644 index 0000000000..cb077a1dad --- /dev/null +++ b/trunk/module/testcase/view/header.html.php @@ -0,0 +1,125 @@ + + + diff --git a/trunk/module/testcase/view/import.html.php b/trunk/module/testcase/view/import.html.php new file mode 100755 index 0000000000..d912cb2c94 --- /dev/null +++ b/trunk/module/testcase/view/import.html.php @@ -0,0 +1,32 @@ + + * @package file + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + importEncodeList,'gbk');?> + +
      +
      + + + + + + + + +
      + +
      +
      + diff --git a/trunk/module/testcase/view/index.html.php b/trunk/module/testcase/view/index.html.php new file mode 100644 index 0000000000..61e8305262 --- /dev/null +++ b/trunk/module/testcase/view/index.html.php @@ -0,0 +1,16 @@ + + * @package bug + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      +
      + \ No newline at end of file diff --git a/trunk/module/testcase/view/view.html.php b/trunk/module/testcase/view/view.html.php new file mode 100644 index 0000000000..c8198b72b9 --- /dev/null +++ b/trunk/module/testcase/view/view.html.php @@ -0,0 +1,199 @@ + + * @package case + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +
      deleted) echo "class='deleted'";?>> +
      CASE #id . ' ' . $case->title;?>
      +
      + session->caseList != false ? $app->session->caseList : $this->createLink('testcase', 'browse', "productID=$case->product"); + if(!$case->deleted) + { + ob_start(); + + common::printIcon('testtask', 'runCase', "runID=0&caseID=$case->id&version=$case->currentVersion"); + common::printIcon('testtask', 'results', "runID=0&caseID=$case->id&version=$case->version"); + + common::printIcon('testcase', 'createBug', "product=$case->product&extra=caseID=$case->id,version=$case->version,runID=", '', 'button', 'createBug'); + + common::printDivider(); + common::printIcon('testcase', 'edit',"caseID=$case->id"); + common::printCommentIcon('testcase'); + common::printIcon('testcase', 'create', "productID=$case->product&moduleID=$case->module&from=testcase¶m=$case->id", '', 'button', 'copy'); + common::printIcon('testcase', 'delete', "caseID=$case->id", '', 'button', '', 'hiddenwin'); + + common::printDivider(); + common::printRPN($browseLink, $preAndNext); + + $actionLinks = ob_get_contents(); + ob_clean(); + echo $actionLinks; + } + ?> +
      +
      + + + + + + + +
      +
      + testcase->precondition;?> + precondition;?> +
      + + + + + + + steps as $stepID => $step) + { + $stepID += 1; + echo ""; + echo ""; + echo ""; + echo ""; + } + ?> +
      testcase->stepID;?>testcase->stepDesc;?>testcase->stepExpect;?>
      $stepID" . nl2br($step->desc) . "" . nl2br($step->expect) . "
      + fetch('file', 'printFiles', array('files' => $case->files, 'fieldset' => 'true'));?> + + + +
      +
      + testcase->legendBasicInfo;?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      testcase->product;?>product", $productName)) echo $productName;?>
      testcase->module;?> + $module) + { + if(!common::printLink('testcase', 'browse', "productID=$case->product&browseType=byModule¶m=$module->id", $module->name)) echo $module->name; + if(isset($modulePath[$key + 1])) echo $lang->arrow; + } +?> +
      testcase->story;?> +storyTitle)) echo html::a($this->createLink('story', 'view', "storyID=$case->story"), "#$case->story:$case->storyTitle"); + if($case->story and $case->storyStatus == 'active' and $case->latestStoryVersion > $case->storyVersion) + { + echo "({$lang->story->changed} "; + echo html::a($this->createLink('testcase', 'confirmStoryChange', "caseID=$case->id"), $lang->confirm, 'hiddenwin'); + echo ")"; + } +?> +
      testcase->type;?>testcase->typeList[$case->type];?>
      testcase->stage;?> +stage) + { + $stags = explode(',', $case->stage); + foreach($stags as $stage) + { + isset($lang->testcase->stageList[$stage]) ? print($lang->testcase->stageList[$stage]) : print($stage); + echo "
      "; + } + } +?> +
      testcase->pri;?>pri;?>
      testcase->status;?>testcase->statusList[$case->status];?>
      app->loadLang('testtask')->testtask->lastRunTime;?>lastRunDate)) echo $case->lastRunDate;?>
      app->loadLang('testtask')->testtask->lastRunResult;?>lastRunResult) echo $lang->testcase->resultList[$case->lastRunResult];?>
      testcase->keywords;?>keywords;?>
      testcase->linkCase;?> +linkCaseTitles)) + { + foreach($case->linkCaseTitles as $linkCaseID => $linkCaseTitle) + { + echo html::a($this->createLink('testcase', 'view', "caseID=$linkCaseID"), "#$linkCaseID $linkCaseTitle", '_blank') . '
      '; + } + } +?> +
      +
      + +
      + testcase->legendOpenAndEdit;?> + + + + + + + + + +
      testcase->openedBy;?>openedBy . $lang->at . $case->openedDate;?>
      testcase->lblLastEdited;?>lastEditedBy) echo $case->lastEditedBy . $lang->at . $case->lastEditedDate;?>
      +
      +
      + testcase->legendVersion;?> +
      + version; $i >= 1; $i --) echo html::a(inlink('view', "caseID=$case->id&version=$i"), '#' . $i) . ' ';?> +
      +
      +
      + diff --git a/trunk/module/testtask/config.php b/trunk/module/testtask/config.php new file mode 100644 index 0000000000..153580ae1d --- /dev/null +++ b/trunk/module/testtask/config.php @@ -0,0 +1,6 @@ +testtask->create->requiredFields = 'project,build,begin,end,name'; +$config->testtask->edit->requiredFields = 'project,build,begin,end,name'; + +$config->testtask->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->testtask->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); diff --git a/trunk/module/testtask/control.php b/trunk/module/testtask/control.php new file mode 100644 index 0000000000..1409cbb1ab --- /dev/null +++ b/trunk/module/testtask/control.php @@ -0,0 +1,491 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +class testtask extends control +{ + public $products = array(); + + /** + * Construct function, load product module, assign products to view auto. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('product'); + $this->view->products = $this->products = $this->product->getPairs(); + } + + /** + * Index page, header to browse. + * + * @access public + * @return void + */ + public function index() + { + $this->locate($this->createLink('testtask', 'browse')); + } + + /** + * Browse test tasks. + * + * @param int $productID + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function browse($productID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save session. */ + $this->session->set('testtaskList', $this->app->getURI(true)); + + /* Set menu. */ + $productID = $this->product->saveState($productID, $this->products); + $this->testtask->setMenu($this->products, $productID); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + $this->view->header->title = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->common; + $this->view->position[] = html::a($this->createLink('testtask', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testtask->common; + $this->view->productID = $productID; + $this->view->productName = $this->products[$productID]; + $this->view->pager = $pager; + $this->view->orderBy = $orderBy; + $this->view->tasks = $this->testtask->getProductTasks($productID); + $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter'); + + $this->display(); + } + + /** + * Create a test task. + * + * @param int $productID + * @access public + * @return void + */ + public function create($productID, $projectID = 0, $build = 0) + { + if(!empty($_POST)) + { + $taskID = $this->testtask->create(); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action')->create('testtask', $taskID, 'opened'); + die(js::locate($this->createLink('testtask', 'browse', "productID=$productID"), 'parent')); + } + + /* Create testtask from build of project.*/ + if($projectID != 0 and $build != 0) + { + $products = $this->dao->select('t2.id, t2.name') + ->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t1.project')->eq($projectID) + ->fetchPairs('id'); + + foreach($products as $key => $value) + { + $productID = $key; + break; + } + + $projects = $this->dao->select('id, name')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetchPairs('id'); + $builds = $this->dao->select('id, name')->from(TABLE_BUILD)->where('id')->eq($build)->fetchPairs('id'); + } + + /* Create testtask from testtask of project.*/ + if($projectID != 0 and $build == 0) + { + $products = $this->dao->select('t2.id, t2.name') + ->from(TABLE_PROJECTPRODUCT)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2') + ->on('t1.product = t2.id') + ->where('t1.project')->eq($projectID) + ->fetchPairs('id'); + + foreach($products as $key => $value) + { + $productID = $key; + break; + } + + $projects = $this->dao->select('id, name')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetchPairs('id'); + $builds = $this->dao->select('id, name')->from(TABLE_BUILD)->where('project')->eq($projectID)->fetchPairs('id'); + } + + /* Create testtask from testtask of test.*/ + if($projectID == 0) + { + $projects = $this->product->getProjectPairs($productID, $params = 'nodeleted'); + $builds = $this->loadModel('build')->getProductBuildPairs($productID); + } + + /* Set menu. */ + $productID = $this->product->saveState($productID, $this->products); + $this->testtask->setMenu($this->products, $productID); + + $this->view->header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->create; + $this->view->position[] = html::a($this->createLink('testtask', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testtask->create; + + if($projectID != 0) + { + $this->view->products = $products; + $this->view->projectID = $projectID; + } + $this->view->projects = $projects; + $this->view->productID = $productID; + $this->view->builds = $builds; + $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted'); + + $this->display(); + } + + /** + * View a test task. + * + * @param int $taskID + * @access public + * @return void + */ + public function view($taskID) + { + /* Get test task, and set menu. */ + $task = $this->testtask->getById($taskID, true); + if(!$task) die(js::error($this->lang->notFound) . js::locate('back')); + $productID = $task->product; + $this->testtask->setMenu($this->products, $productID); + + $this->view->header['title'] = "TASK #$task->id $task->name/" . $this->products[$productID]; + $this->view->position[] = html::a($this->createLink('testtask', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testtask->view; + + $this->view->productID = $productID; + $this->view->task = $task; + $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter'); + $this->view->actions = $this->loadModel('action')->getList('testtask', $taskID); + + $this->display(); + } + + /** + * Browse cases of a test task. + * + * @param string $taskID + * @param string $browseType bymodule|all|assignedtome + * @param int $param + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function cases($taskID, $browseType = 'byModule', $param = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + /* Save the session. */ + $this->app->loadLang('testcase'); + $this->session->set('caseList', $this->app->getURI(true)); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + /* Set the browseType and moduleID. */ + $browseType = strtolower($browseType); + $moduleID = ($browseType == 'bymodule') ? (int)$param : 0; + + /* Get task and product info, set menu. */ + $task = $this->testtask->getById($taskID); + if(!$task) die(js::error($this->lang->notFound) . js::locate('back')); + $productID = $task->product; + $this->testtask->setMenu($this->products, $productID); + if($browseType == 'bymodule' or $browseType == 'all') + { + $modules = ''; + if($moduleID) $modules = $this->loadModel('tree')->getAllChildID($moduleID); + $this->view->runs = $this->testtask->getRuns($taskID, $modules, $orderBy, $pager); + } + elseif($browseType == 'assignedtome') + { + $this->view->runs = $this->testtask->getUserRuns($taskID, $this->session->user->account, $orderBy, $pager); + } + + /* Save testcaseIDs session for get the pre and next testcase. */ + $testcaseIDs = ''; + foreach($this->view->runs as $run) $testcaseIDs .= ',' . $run->case; + $this->session->set('testcaseIDs', $testcaseIDs . ','); + + $this->view->header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->cases; + $this->view->position[] = html::a($this->createLink('testtask', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testtask->cases; + $this->view->productID = $productID; + $this->view->productName = $this->products[$productID]; + $this->view->task = $task; + $this->view->users = $this->loadModel('user')->getPairs('noclosed'); + $this->view->moduleTree = $this->loadModel('tree')->getTreeMenu($productID, $viewType = 'case', $startModuleID = 0, array('treeModel', 'createTestTaskLink'), $extra = $taskID); + $this->view->browseType = $browseType; + $this->view->param = $param; + $this->view->orderBy = $orderBy; + $this->view->taskID = $taskID; + $this->view->moduleID = $moduleID; + $this->view->treeClass = $browseType == 'bymodule' ? '' : 'hidden'; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * Edit a test task. + * + * @param int $taskID + * @access public + * @return void + */ + public function edit($taskID) + { + if(!empty($_POST)) + { + $changes = $this->testtask->update($taskID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('testtask', $taskID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate(inlink('view', "taskID=$taskID"), 'parent')); + } + + /* Get task info. */ + $task = $this->testtask->getById($taskID); + $productID = $this->product->saveState($task->product, $this->products); + + /* Set menu. */ + $this->testtask->setMenu($this->products, $productID); + + $this->view->header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->edit; + $this->view->position[] = html::a($this->createLink('testtask', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testtask->edit; + + $this->view->task = $task; + $this->view->projects = $this->product->getProjectPairs($productID); + $this->view->builds = $this->loadModel('build')->getProductBuildPairs($productID); + $this->view->users = $this->loadModel('user')->getPairs(); + + $this->display(); + } + + /** + * Delete a test task. + * + * @param int $taskID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($taskID, $confirm = 'no') + { + if($confirm == 'no') + { + die(js::confirm($this->lang->testtask->confirmDelete, inlink('delete', "taskID=$taskID&confirm=yes"))); + } + else + { + $task = $this->testtask->getByID($taskID); + $this->testtask->delete(TABLE_TESTTASK, $taskID); + die(js::locate(inlink('browse', "product=$task->product"), 'parent')); + } + } + + /** + * Link cases to a test task. + * + * @param int $taskID + * @access public + * @return void + */ + public function linkCase($taskID, $param = 'all', $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + if(!empty($_POST)) + { + $this->testtask->linkCase($taskID); + $this->locate(inlink('cases', "taskID=$taskID")); + } + + /* Save session. */ + $this->session->set('caseList', $this->app->getURI(true)); + + /* Get task and product id. */ + $task = $this->testtask->getById($taskID); + $productID = $this->product->saveState($task->product, $this->products); + + /* Load pager. */ + $this->app->loadClass('pager', $static = true); + $pager = pager::init($recTotal, $recPerPage, $pageID); + + /* Build the search form. */ + $this->loadModel('testcase'); + $this->config->testcase->search['params']['product']['values']= array($productID => $this->products[$productID], 'all' => $this->lang->testcase->allProduct); + $this->config->testcase->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'case'); + $this->config->testcase->search['actionURL'] = inlink('linkcase', "taskID=$taskID"); + $this->view->searchForm = $this->fetch('search', 'buildForm', $this->config->testcase->search); + + /* Save session. */ + $this->testtask->setMenu($this->products, $productID); + + $this->view->header['title'] = $this->products[$productID] . $this->lang->colon . $this->lang->testtask->linkCase; + $this->view->position[] = html::a($this->createLink('testtask', 'browse', "productID=$productID"), $this->products[$productID]); + $this->view->position[] = $this->lang->testtask->linkCase; + + /* Get cases. */ + if($this->session->testcaseQuery == false) $this->session->set('testcaseQuery', ' 1 = 1'); + $query = str_replace("`product` = 'all'", '1', $this->session->testcaseQuery); // If search all product, replace product = all to 1=1 + $linkedCases = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->fetchPairs('case'); + if($param == 'all') + { + $cases = $this->dao->select('*')->from(TABLE_CASE)->where($query) + ->andWhere('product')->eq($productID) + ->andWhere('id')->notIN($linkedCases) + ->andWhere('deleted')->eq(0) + ->orderBy('id desc') + ->page($pager) + ->fetchAll(); + } + if($param == 'bystory') + { + $stories = $this->dao->select('stories')->from(TABLE_BUILD)->where('id')->eq($task->build)->fetch('stories'); + + $cases = $this->dao->select('*')->from(TABLE_CASE)->where($query) + ->andWhere('product')->eq($productID) + ->beginIF($linkedCases)->andWhere('id')->notIN($linkedCases)->fi() + ->andWhere('story')->in($stories) + ->andWhere('deleted')->eq(0) + ->orderBy('id desc') + ->page($pager) + ->fetchAll(); + } + if($param == 'bybug') + { + $bugs = $this->dao->select('bugs')->from(TABLE_BUILD)->where('id')->eq($task->build)->fetch('bugs'); + $cases = $this->dao->select('*')->from(TABLE_CASE)->where($query) + ->andWhere('product')->eq($productID) + ->beginIF($linkedCases)->andWhere('id')->notIN($linkedCases)->fi() + ->andWhere('fromBug')->in($bugs) + ->andWhere('deleted')->eq(0) + ->orderBy('id desc') + ->page($pager) + ->fetchAll(); + } + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->cases = $cases; + $this->view->taskID = $taskID; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * Remove a case from test task. + * + * @param int $rowID + * @access public + * @return void + */ + public function unlinkCase($rowID) + { + $this->dao->delete()->from(TABLE_TESTRUN)->where('id')->eq((int)$rowID)->exec(); + die(js::reload('parent')); + } + + /** + * Run case. + * + * @param int $runID + * @param String $extras others params, forexample, caseID=10, version=3 + * @access public + * @return void + */ + public function runCase($runID, $caseID = 0, $version = 0) + { + if(!empty($_POST)) + { + $this->testtask->createResult($runID); + if(dao::isError()) die(js::error(dao::getError())); + echo js::reload('parent'); + die(js::closeWindow()); + } + + if(!$caseID) $run = $this->testtask->getRunById($runID); + if($caseID) $run->case = $this->loadModel('testcase')->getById($caseID, $version); + + $this->view->run = $run; + + die($this->display()); + } + + /** + * View test results of a test run. + * + * @param int $runID + * @param int $caseID + * @access public + * @return void + */ + public function results($runID, $caseID = 0, $version = 0) + { + if($caseID) + { + $case = $this->loadModel('testcase')->getByID($caseID, $version); + $results = $this->testtask->getResults(0, $caseID); + } + else + { + $case = $this->testtask->getRunById($runID)->case; + $results = $this->testtask->getResults($runID); + + $testtaskID = $this->dao->select('task')->from(TABLE_TESTRUN)->where('id')->eq($runID)->fetch('task', false); + $testtask = $this->dao->select('build, product')->from(TABLE_TESTTASK)->where('id')->eq($testtaskID)->fetch(); + $builds = $this->loadModel('build')->getProductBuildPairs($testtask->product); + $this->view->build = isset($builds[$testtask->build]) ? $builds[$testtask->build] : ''; + } + + $this->view->case = $case; + $this->view->results = $results; + $this->view->users = $this->loadModel('user')->getPairs('noclosed, noletter'); + + die($this->display()); + } + + /** + * Batch assign cases. + * + * @param int $taskID + * @access public + * @return void + */ + public function batchAssign($taskID) + { + $this->dao->update(TABLE_TESTRUN) + ->set('assignedTo')->eq($this->post->assignedTo) + ->where('task')->eq((int)$taskID) + ->andWhere('`case`')->in($this->post->caseIDList) + ->exec(); + die(js::locate($this->session->caseList)); + } +} diff --git a/trunk/module/testtask/css/results.css b/trunk/module/testtask/css/results.css new file mode 100644 index 0000000000..620aa31b05 --- /dev/null +++ b/trunk/module/testtask/css/results.css @@ -0,0 +1 @@ +body{background:white} diff --git a/trunk/module/testtask/css/runcase.css b/trunk/module/testtask/css/runcase.css new file mode 100644 index 0000000000..620aa31b05 --- /dev/null +++ b/trunk/module/testtask/css/runcase.css @@ -0,0 +1 @@ +body{background:white} diff --git a/trunk/module/testtask/js/cases.js b/trunk/module/testtask/js/cases.js new file mode 100644 index 0000000000..d15a42543e --- /dev/null +++ b/trunk/module/testtask/js/cases.js @@ -0,0 +1,14 @@ +function browseByModule(active) +{ + $('.side').removeClass('hidden'); + $('.divider').removeClass('hidden'); + $('#bymoduleTab').addClass('active'); + $('#' + active + 'Tab').removeClass('active'); +} + +$(document).ready(function() +{ + $("a.iframe").colorbox({width:900, height:600, iframe:true, transition:'none'}); + $('#' + browseType + 'Tab').addClass('active'); + $('#module' + moduleID).addClass('active'); +}); diff --git a/trunk/module/testtask/js/create.js b/trunk/module/testtask/js/create.js new file mode 100755 index 0000000000..4ce4791007 --- /dev/null +++ b/trunk/module/testtask/js/create.js @@ -0,0 +1,27 @@ +/** + * Load project related builds + * + * @param int $projectID + * @access public + * @return void + */ +function loadProjectRelated(projectID) +{ + loadProjectBuilds(projectID); +} + +/** + * Load project builds. + * + * @param int $projectID + * @access public + * @return void + */ +function loadProjectBuilds(projectID) +{ + selectedBuild = $('#openedBuild').val(); + if(!selectedBuild) selectedBuild = 0; + link = createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + $('#product').val() + '&varName=testTaskBuild&builds=' + selectedBuild); + $('#buildBox').load(link); +} + diff --git a/trunk/module/testtask/lang/en.php b/trunk/module/testtask/lang/en.php new file mode 100644 index 0000000000..326b55082a --- /dev/null +++ b/trunk/module/testtask/lang/en.php @@ -0,0 +1,69 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->testtask->index = "Index"; +$lang->testtask->create = "Create"; +$lang->testtask->delete = "Delete"; +$lang->testtask->view = "Info"; +$lang->testtask->edit = "Edit"; +$lang->testtask->browse = "Testtask browse"; +$lang->testtask->linkCase = "Link case"; +$lang->testtask->linkCaseAB = "Link"; +$lang->testtask->unlinkCase = "Del"; +$lang->testtask->batchAssign = "Batch Assign"; +$lang->testtask->runCase = "Run"; +$lang->testtask->results = "Result"; +$lang->testtask->createBug = "Bug(+)"; +$lang->testtask->assign = 'Assign'; +$lang->testtask->cases = 'Cases'; + +$lang->testtask->common = 'Test task'; +$lang->testtask->id = 'ID'; +$lang->testtask->product = 'Product'; +$lang->testtask->project = 'Project'; +$lang->testtask->build = 'Build'; +$lang->testtask->owner = 'Owner'; +$lang->testtask->name = 'Name'; +$lang->testtask->begin = 'Begin'; +$lang->testtask->end = 'End'; +$lang->testtask->desc = 'Desc'; +$lang->testtask->status = 'Status'; +$lang->testtask->assignedTo = 'Assign'; +$lang->testtask->linkVersion = 'Version'; +$lang->testtask->lastRunAccount = "Run"; +$lang->testtask->lastRunTime = 'Time'; +$lang->testtask->lastRunResult = 'Result'; + +$lang->testtask->statusList['wait'] = 'Pending'; +$lang->testtask->statusList['doing'] = 'In progress'; +$lang->testtask->statusList['done'] = 'Done'; +$lang->testtask->statusList['blocked'] = 'Blocked'; + +$lang->testtask->unlinkedCases = 'Unlinked'; +$lang->testtask->linkedCases = 'Linked'; +$lang->testtask->linkByStory = 'Link by story'; +$lang->testtask->linkByBug = 'Link by bug'; +$lang->testtask->confirmDelete = 'Are you sure to delete this test task?'; +$lang->testtask->passAll = 'Pass all'; +$lang->testtask->pass = 'Pass'; +$lang->testtask->fail = 'Fail'; + +$lang->testtask->byModule = 'By module'; +$lang->testtask->assignedToMe = 'Assgined to me'; +$lang->testtask->allCases = 'All Cases'; + +$lang->testtask->lblCases = 'Case list'; +$lang->testtask->lblUnlinkCase = 'Remove case'; +$lang->testtask->lblRunCase = 'Run'; +$lang->testtask->lblResults = 'Result'; + +$lang->testtask->placeholder->begin = 'begin date'; +$lang->testtask->placeholder->end = 'end date'; diff --git a/trunk/module/testtask/lang/zh-cn.php b/trunk/module/testtask/lang/zh-cn.php new file mode 100644 index 0000000000..f55c31eac0 --- /dev/null +++ b/trunk/module/testtask/lang/zh-cn.php @@ -0,0 +1,66 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->testtask->index = "测试任务首页"; +$lang->testtask->create = "提交测试"; +$lang->testtask->delete = "删除测试任务"; +$lang->testtask->view = "详情"; +$lang->testtask->edit = "编辑测试任务"; +$lang->testtask->browse = "待测列表"; +$lang->testtask->linkCase = "关联用例"; +$lang->testtask->linkCaseAB = "关联"; +$lang->testtask->unlinkCase = "移除"; +$lang->testtask->batchAssign = "批量指派"; +$lang->testtask->runCase = "执行"; +$lang->testtask->results = "结果"; +$lang->testtask->createBug = "提Bug"; +$lang->testtask->assign = '指派'; +$lang->testtask->cases = '用例'; + +$lang->testtask->common = '测试任务'; +$lang->testtask->id = '任务编号'; +$lang->testtask->product = '所属产品'; +$lang->testtask->project = '所属项目'; +$lang->testtask->build = '版本'; +$lang->testtask->owner = '负责人'; +$lang->testtask->name = '任务名称'; +$lang->testtask->begin = '开始日期'; +$lang->testtask->end = '结束日期'; +$lang->testtask->desc = '任务描述'; +$lang->testtask->status = '当前状态'; +$lang->testtask->assignedTo = '指派给'; +$lang->testtask->linkVersion = '版本'; +$lang->testtask->lastRunAccount = '执行人'; +$lang->testtask->lastRunTime = '执行时间'; +$lang->testtask->lastRunResult = '结果'; + +$lang->testtask->statusList['wait'] = '未开始'; +$lang->testtask->statusList['doing'] = '进行中'; +$lang->testtask->statusList['done'] = '已完成'; +$lang->testtask->statusList['blocked'] = '被阻塞'; + +$lang->testtask->unlinkedCases = '未关联'; +$lang->testtask->linkedCases = '已关联'; +$lang->testtask->linkByStory = '按需求关联'; +$lang->testtask->linkByBug = '按Bug关联'; +$lang->testtask->confirmDelete = '您确认要删除该测试任务吗?'; +$lang->testtask->passAll = '全部通过'; +$lang->testtask->pass = '通过'; +$lang->testtask->fail = '失败'; + +$lang->testtask->byModule = '按模块'; +$lang->testtask->assignedToMe = '指派给我'; +$lang->testtask->allCases = '所有Case'; + +$lang->testtask->lblCases = '用例列表'; +$lang->testtask->lblUnlinkCase = '移除用例'; +$lang->testtask->lblRunCase = '执行用例'; +$lang->testtask->lblResults = '执行结果'; diff --git a/trunk/module/testtask/lang/zh-tw.php b/trunk/module/testtask/lang/zh-tw.php new file mode 100644 index 0000000000..8f2cb41593 --- /dev/null +++ b/trunk/module/testtask/lang/zh-tw.php @@ -0,0 +1,66 @@ + + * @package testtask + * @version $Id: zh-tw.php 3238 2012-07-02 01:44:49Z wwccss $ + * @link http://www.zentao.net + */ +$lang->testtask->index = "測試任務首頁"; +$lang->testtask->create = "提交測試"; +$lang->testtask->delete = "刪除測試任務"; +$lang->testtask->view = "詳情"; +$lang->testtask->edit = "編輯測試任務"; +$lang->testtask->browse = "待測列表"; +$lang->testtask->linkCase = "關聯用例"; +$lang->testtask->linkCaseAB = "關聯"; +$lang->testtask->unlinkCase = "移除"; +$lang->testtask->batchAssign = "批量指派"; +$lang->testtask->runCase = "執行"; +$lang->testtask->results = "結果"; +$lang->testtask->createBug = "提Bug"; +$lang->testtask->assign = '指派'; +$lang->testtask->cases = '用例'; + +$lang->testtask->common = '測試任務'; +$lang->testtask->id = '任務編號'; +$lang->testtask->product = '所屬產品'; +$lang->testtask->project = '所屬項目'; +$lang->testtask->build = '版本'; +$lang->testtask->owner = '負責人'; +$lang->testtask->name = '任務名稱'; +$lang->testtask->begin = '開始日期'; +$lang->testtask->end = '結束日期'; +$lang->testtask->desc = '任務描述'; +$lang->testtask->status = '當前狀態'; +$lang->testtask->assignedTo = '指派給'; +$lang->testtask->linkVersion = '版本'; +$lang->testtask->lastRunAccount = '執行人'; +$lang->testtask->lastRunTime = '執行時間'; +$lang->testtask->lastRunResult = '結果'; + +$lang->testtask->statusList['wait'] = '未開始'; +$lang->testtask->statusList['doing'] = '進行中'; +$lang->testtask->statusList['done'] = '已完成'; +$lang->testtask->statusList['blocked'] = '被阻塞'; + +$lang->testtask->unlinkedCases = '未關聯'; +$lang->testtask->linkedCases = '已關聯'; +$lang->testtask->linkByStory = '按需求關聯'; +$lang->testtask->linkByBug = '按Bug關聯'; +$lang->testtask->confirmDelete = '您確認要刪除該測試任務嗎?'; +$lang->testtask->passAll = '全部通過'; +$lang->testtask->pass = '通過'; +$lang->testtask->fail = '失敗'; + +$lang->testtask->byModule = '按模組'; +$lang->testtask->assignedToMe = '指派給我'; +$lang->testtask->allCases = '所有Case'; + +$lang->testtask->lblCases = '用例列表'; +$lang->testtask->lblUnlinkCase = '移除用例'; +$lang->testtask->lblRunCase = '執行用例'; +$lang->testtask->lblResults = '執行結果'; diff --git a/trunk/module/testtask/model.php b/trunk/module/testtask/model.php new file mode 100644 index 0000000000..8e2c194ce7 --- /dev/null +++ b/trunk/module/testtask/model.php @@ -0,0 +1,356 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> +loadModel('product')->setMenu($products, $productID); + $selectHtml = $this->product->select($products, $productID, 'testtask', 'browse'); + foreach($this->lang->testtask->menu as $key => $value) + { + $replace = ($key == 'product') ? $selectHtml : $productID; + common::setMenuVars($this->lang->testtask->menu, $key, $replace); + } + } + + /** + * Create a test task. + * + * @param int $productID + * @access public + * @return void + */ + function create() + { + $task = fixer::input('post') + ->stripTags('name') + ->get(); + $this->dao->insert(TABLE_TESTTASK)->data($task)->autoCheck()->batchcheck($this->config->testtask->create->requiredFields, 'notempty')->exec(); + if(!dao::isError()) return $this->dao->lastInsertID(); + } + + /** + * Get test tasks of a product. + * + * @param int $productID + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getProductTasks($productID, $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('t1.*, t2.name AS productName, t3.name AS projectName, t4.name AS buildName') + ->from(TABLE_TESTTASK)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') + ->leftJoin(TABLE_PROJECT)->alias('t3')->on('t1.project = t3.id') + ->leftJoin(TABLE_BUILD)->alias('t4')->on('t1.build = t4.id') + ->where('t1.product')->eq((int)$productID) + ->andWhere('t1.deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + } + + /** + * Get test tasks of a project. + * + * @param int $projectID + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getProjectTasks($projectID, $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('t1.*, t2.name AS buildName') + ->from(TABLE_TESTTASK)->alias('t1') + ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build = t2.id') + ->where('t1.project')->eq((int)$projectID) + ->andWhere('t1.deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + } + + /** + * Get test task info by id. + * + * @param int $taskID + * @param bool $setImgSize + * @access public + * @return void + */ + public function getById($taskID, $setImgSize = false) + { + $task = $this->dao->select('t1.*, t2.name AS productName, t3.name AS projectName, t4.name AS buildName') + ->from(TABLE_TESTTASK)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') + ->leftJoin(TABLE_PROJECT)->alias('t3')->on('t1.project = t3.id') + ->leftJoin(TABLE_BUILD)->alias('t4')->on('t1.build = t4.id') + ->where('t1.id')->eq((int)$taskID)->fetch(); + if($setImgSize) $task->desc = $this->loadModel('file')->setImgSize($task->desc); + return $task; + } + + /** + * Get test tasks by user. + * + * @param string $account + * @access public + * @return array + */ + public function getByUser($account) + { + return $this->dao->select('t1.*, t2.name AS projectName, t3.name AS buildName') + ->from(TABLE_TESTTASK)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->leftJoin(TABLE_BUILD)->alias('t3')->on('t1.build = t3.id') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.owner')->eq($account) + ->orderBy('id desc') + ->fetchAll(); + } + + /** + * Update a test task. + * + * @param int $taskID + * @access public + * @return void + */ + public function update($taskID) + { + $oldTask = $this->getById($taskID); + $task = fixer::input('post')->stripTags('name')->get(); + $this->dao->update(TABLE_TESTTASK)->data($task)->autoCheck()->batchcheck($this->config->testtask->edit->requiredFields, 'notempty')->where('id')->eq($taskID)->exec(); + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + + /** + * Link cases. + * + * @param int $taskID + * @access public + * @return void + */ + public function linkCase($taskID) + { + if($this->post->cases == false) return; + foreach($this->post->cases as $caseID) + { + $row->task = $taskID; + $row->case = $caseID; + $row->version = $this->post->versions[$caseID]; + $row->assignedTo = ''; + $row->status = 'wait'; + $this->dao->replace(TABLE_TESTRUN)->data($row)->exec(); + } + } + + /** + * Get test runs of a test task. + * + * @param int $taskID + * @param int $moduleID + * @param object $pager + * @access public + * @return array + */ + public function getRuns($taskID, $moduleID, $orderBy, $pager = null) + { + $orderBy = strpos($orderBy, 'assignedTo') !== false ? ('t1.' . $orderBy) : ('t2.' . $orderBy); + + return $this->dao->select('t2.*,t1.*')->from(TABLE_TESTRUN)->alias('t1') + ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id') + ->where('t1.task')->eq((int)$taskID) + ->beginIF($moduleID)->andWhere('t2.module')->in($moduleID)->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('', false); + } + + /** + * Get test runs of a user. + * + * @param int $taskID + * @param int $user + * @param obejct $pager + * @access public + * @return array + */ + public function getUserRuns($taskID, $user, $orderBy, $pager = null) + { + $orderBy = strpos($orderBy, 'assignedTo') !== false ? ('t1.' . $orderBy) : ('t2.' . $orderBy); + + return $this->dao->select('t2.*,t1.*')->from(TABLE_TESTRUN)->alias('t1') + ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id') + ->where('t1.task')->eq((int)$taskID) + ->andWhere('t1.assignedTo')->eq($user) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll(); + } + + /** + * Get info of a test run. + * + * @param int $runID + * @access public + * @return void + */ + public function getRunById($runID) + { + $testRun = $this->dao->findById($runID)->from(TABLE_TESTRUN)->fetch(); + $testRun->case = $this->loadModel('testcase')->getById($testRun->case, $testRun->version); + return $testRun; + } + + /** + * Create test result + * + * @param int $runID + * @access public + * @return void + */ + public function createResult($runID = 0) + { + /* Compute the test result. + * + * 1. if there result in the post, use it. + * 2. if no result, set default is pass. + * 3. then check the steps to compute result. + * + * */ + $caseResult = $this->post->result ? $this->post->result : 'pass'; + if(isset($_POST['passall']) and $this->post->passall == false) + { + if($this->post->steps) + { + foreach($this->post->steps as $stepID => $stepResult) + { + if($stepResult != 'pass' and $stepResult != 'n/a') + { + $caseResult = $stepResult; + break; + } + } + } + } + + /* Create result of every step. */ + if($this->post->steps) + { + foreach($this->post->steps as $stepID =>$stepResult) + { + $step['result'] = $stepResult; + $step['real'] = $this->post->reals[$stepID]; + $stepResults[$stepID] = $step; + } + } + else + { + $stepResults = array(); + } + + /* Insert into testResult table. */ + $now = helper::now(); + $result = fixer::input('post') + ->add('run', $runID) + ->add('caseResult', $caseResult) + ->setForce('stepResults', serialize($stepResults)) + ->add('lastRunner', $this->app->user->account) + ->add('date', $now) + ->remove('steps,reals,passall,result') + ->get(); + $this->dao->insert(TABLE_TESTRESULT)->data($result)->autoCheck()->exec(); + $this->dao->update(TABLE_CASE)->set('lastRunner')->eq($this->app->user->account)->set('lastRunDate')->eq($now)->set('lastRunResult')->eq($caseResult)->where('id')->eq($this->post->case)->exec(); + + if($runID) + { + /* Update testRun's status. */ + if(!dao::isError()) + { + $runStatus = $caseResult == 'blocked' ? 'blocked' : 'done'; + $this->dao->update(TABLE_TESTRUN) + ->set('lastRunResult')->eq($caseResult) + ->set('status')->eq($runStatus) + ->set('lastRunner')->eq($this->app->user->account) + ->set('lastRunDate')->eq($now) + ->where('id')->eq($runID) + ->exec(); + } + } + } + + /** + * Get results by runID or caseID + * + * @param int $runID + * @param int $caseID + * @access public + * @return array + */ + public function getResults($runID, $caseID = 0) + { + if($caseID > 0) + { + $results = $this->dao->select('*')->from(TABLE_TESTRESULT)->where('`case`')->eq($caseID)->orderBy('id desc')->fetchAll('id'); + } + else + { + $results = $this->dao->select('*')->from(TABLE_TESTRESULT)->where('run')->eq($runID)->orderBy('id desc')->fetchAll('id'); + } + + if(!$results) return array(); + + $relatedVersions = array(); + foreach($results as $result) + { + $relatedVersions[] = $result->version; + $runCaseID = $result->case; + } + $relatedVersions = array_unique($relatedVersions); + + $relatedSteps = $this->dao->select('*')->from(TABLE_CASESTEP) + ->beginIF($caseID)->where('`case`')->eq($caseID)->fi() + ->beginIF($runID)->where('`case`')->eq($runCaseID)->fi() + ->andWhere('version')->in($relatedVersions) + ->fetchAll(); + + foreach($results as $resultID => $result) + { + $result->stepResults = unserialize($result->stepResults); + $results[$resultID] = $result; + + foreach($relatedSteps as $key => $step) + { + if($result->version == $step->version) + { + $result->stepResults[$step->id]['desc'] = $step->desc; + $result->stepResults[$step->id]['expect'] = $step->expect; + } + } + } + return $results; + } +} diff --git a/trunk/module/testtask/view/browse.html.php b/trunk/module/testtask/view/browse.html.php new file mode 100644 index 0000000000..2808092c57 --- /dev/null +++ b/trunk/module/testtask/view/browse.html.php @@ -0,0 +1,56 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      testtask->browse;?>
      +
      testtask->create);?>
      +
      idAB;?>testtask->name;?>testtask->project;?>testtask->build;?>testtask->owner;?>testtask->begin;?>testtask->end;?>statusAB;?>actions;?>
      id"), sprintf('%03d', $task->id));?>id"), $task->name);?>projectName?>build == 'trunk' ? print('Trunk') : print(html::a($this->createLink('build', 'view', "buildID=$task->build"), $task->buildName));?>owner];?>begin?>end?>testtask->statusList[$task->status];?> + id", $lang->testtask->cases); + common::printLink('testtask', 'linkcase', "taskID=$task->id", $lang->testtask->linkCaseAB); + common::printIcon('testtask', 'edit', "taskID=$task->id", '', 'list'); + common::printIcon('testtask', 'delete', "taskID=$task->id", '', 'list', '', 'hiddenwin'); + ?> +
      + diff --git a/trunk/module/testtask/view/cases.html.php b/trunk/module/testtask/view/cases.html.php new file mode 100644 index 0000000000..f8196dbc21 --- /dev/null +++ b/trunk/module/testtask/view/cases.html.php @@ -0,0 +1,106 @@ + + * @package case + * @version $Id: view.html.php 594 2010-03-27 13:44:07Z wwccss $ + * @link http://www.zentao.net + */ +?> + + + + +
      +
      + " . $lang->testtask->byModule . " "; + echo "" . html::a($this->inlink('cases', "taskID=$taskID&browseType=all¶m=0"), $lang->testtask->allCases) . ""; + echo "" . html::a($this->inlink('cases', "taskID=$taskID&browseType=assignedtome¶m=0"), $lang->testtask->assignedToMe) . ""; + ?> +
      +
      + id"); + common::printIcon('testcase', 'export', "productID=$productID&orderBy=$orderBy&taskID=$task->id"); + common::printDivider(); + common::printRPN($this->session->testtaskList, ''); + ?> +
      +
      + +
      + + + + + + +
      +
      +
      +
      + id&browseType=$browseType¶m=$param&orderBy=%s&recToal={$pager->recTotal}&recPerPage={$pager->recPerPage}"; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      idAB);?> priAB);?> testcase->title);?> testcase->type);?> testtask->assignedTo);?> testtask->lastRunAccount);?> testtask->lastRunTime);?> testtask->lastRunResult);?> statusAB);?>actions;?>
      case);?>pri?>createLink('testcase', 'view', "caseID=$run->case&version=$run->version"), $run->title, '_blank');?> + testcase->typeList[$run->type];?>assignedTo]; echo substr($assignedTo, strpos($assignedTo, ':') + 1);?>lastRunner]; echo substr($lastRunner, strpos($lastRunner, ':') + 1);?>lastRunDate)) echo date(DT_MONTHTIME1, strtotime($run->lastRunDate));?>lastRunResult) echo $lang->testcase->resultList[$run->lastRunResult];?>testtask->statusList[$run->status];?> + id", $lang->testtask->runCase, '', 'class="iframe"'); + common::printLink('testtask', 'results', "id=$run->id", $lang->testtask->results, '', 'class="iframe"'); + common::printLink('testtask', 'unlinkcase', "id=$run->id", $lang->testtask->unlinkCase, 'hiddenwin'); + common::printIcon('testcase', 'createBug', "product=$productID&extra=projectID=$task->project,buildID=$task->build,caseID=$run->case,runID=$run->id", $run, 'list', 'createBug'); + ?> +
      +
      + + + + id");?>';casesform.submit();"> + +
      + show();?> +
      +
      +
      + diff --git a/trunk/module/testtask/view/create.html.php b/trunk/module/testtask/view/create.html.php new file mode 100644 index 0000000000..4b078494d0 --- /dev/null +++ b/trunk/module/testtask/view/create.html.php @@ -0,0 +1,67 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      testtask->create;?>
      testtask->product;?>
      testtask->project;?>
      testtask->build;?>
      testtask->owner;?>
      testtask->begin;?> +
      testtask->end;?> +
      testtask->status;?>testtask->statusList, '', "class='select-3'");?> +
      testtask->name;?> +
      testtask->desc;?> +
      +
      + diff --git a/trunk/module/testtask/view/edit.html.php b/trunk/module/testtask/view/edit.html.php new file mode 100644 index 0000000000..ff0a24197e --- /dev/null +++ b/trunk/module/testtask/view/edit.html.php @@ -0,0 +1,56 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      testtask->edit;?>
      testtask->project;?>project, 'class=select-3');?>
      testtask->build;?>build, 'class=select-3');?>
      testtask->owner;?>owner, 'class=select-3');?>
      testtask->begin;?>begin, "class='text-3 date'");?> +
      testtask->end;?>end, "class='text-3 date'");?> +
      testtask->status;?>testtask->statusList, $task->status, "class='select-3'");?> +
      testtask->name;?>name, "class='text-1'");?> +
      testtask->desc;?>desc), "rows=10 class='area-1'");?> +
      +
      + diff --git a/trunk/module/testtask/view/linkcase.html.php b/trunk/module/testtask/view/linkcase.html.php new file mode 100644 index 0000000000..c475a0cf74 --- /dev/null +++ b/trunk/module/testtask/view/linkcase.html.php @@ -0,0 +1,70 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      testtask->unlinkedCases;?>
      +
      session->testtaskList, $lang->goback);?>
      +
      createLink('testtask', 'linkcase', "taskID=$taskID¶m=bybug"), $lang->testtask->linkByBug);?>
      +
      createLink('testtask', 'linkcase', "taskID=$taskID¶m=bystory"), $lang->testtask->linkByStory);?>
      +
      idAB;?>testtask->linkVersion;?>priAB;?>testcase->title;?>testcase->type;?>openedByAB;?>statusAB;?>
      + + createLink('testcase', 'view', "testcaseID=$case->id"), sprintf('%03d', $case->id));?> + id]", array_combine(range($case->version, 1), range($case->version, 1)), '', 'class=select-1');?> pri?> + title . ' ( '; + for($i = $case->version; $i >= 1; $i --) + { + echo html::a($this->createLink('testcase', 'view', "caseID=$case->id&version=$i"), "#$i", '_blank'); + } + echo ')'; + ?> + testcase->typeList[$case->type];?>openedBy];?>testcase->statusList[$case->status];?>
      +
      +
      show();?>
      +
      +
      + diff --git a/trunk/module/testtask/view/results.html.php b/trunk/module/testtask/view/results.html.php new file mode 100644 index 0000000000..9f3bf94e94 --- /dev/null +++ b/trunk/module/testtask/view/results.html.php @@ -0,0 +1,52 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +

      CASE#id. $lang->colon . $case->title;?>

      +
      + testcase->precondition;?> + precondition;?> +
      + + + + + + + + + + + stepResults as $key => $stepResult): + ?> + + + + + stepResults)):?> + + + + + + + + + +
      +
      RESULT#id . ' ' . $result->date . ' ' . $users[$result->lastRunner] . ' ' . $lang->testtask->runCase . ':'. " " . $lang->testcase->resultList[$result->caseResult] . '';?>
      +
      testtask->build . $lang->colon . $build;?>
      +
      testcase->stepID;?>testcase->stepDesc;?>testcase->stepExpect;?>testcase->result;?>testcase->real;?>
      a-center'>testcase->resultList[$stepResult['result']];?>
      + + diff --git a/trunk/module/testtask/view/runcase.html.php b/trunk/module/testtask/view/runcase.html.php new file mode 100644 index 0000000000..f206738fd8 --- /dev/null +++ b/trunk/module/testtask/view/runcase.html.php @@ -0,0 +1,59 @@ + + * @package testtask + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
      + + + + + + + + + + + + + case->steps as $key => $step):?> + expect ? 'pass' : 'n/a';?> + + + + + + + + + + + +
      CASE#case->id. $lang->colon . $run->case->title;?>
      testcase->precondition;?>
      case->precondition;?>
      testcase->stepID;?>testcase->stepDesc;?>testcase->stepExpect;?>testcase->result;?>testcase->real;?>
      desc);?>expect);?>id]", $lang->testcase->resultList, $defaultResult);?>id]", '', "rows=3 class='area-1'");?>
      + case->steps)) + { + echo html::submitButton($lang->testtask->pass, "onclick=$('#result').val('pass')"); + echo html::submitButton($lang->testtask->fail, "onclick=$('#result').val('fail')"); + } + else + { + echo html::submitButton(); + echo html::submitButton($lang->testtask->passAll, "onclick=$('#passall').val(1)"); + } + echo html::hidden('case', $run->case->id); + echo html::hidden('version', $run->case->version); + if($run->case->steps) echo html::hidden('passall', 0); + if(!$run->case->steps) echo html::hidden('result', ''); + ?> +
      +
      + diff --git a/trunk/module/testtask/view/view.html.php b/trunk/module/testtask/view/view.html.php new file mode 100644 index 0000000000..22bfb0fc0a --- /dev/null +++ b/trunk/module/testtask/view/view.html.php @@ -0,0 +1,64 @@ + + * @package case + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TASK #id . ' ' . $task->name;?>
      testtask->name;?>'>name;?> +
      testtask->project;?>projectName;?>
      testtask->build;?>buildName ? print($task->buildName) : print($task->build);?>
      testtask->owner;?>owner];?>
      testtask->begin;?>begin;?>
      testtask->end;?>end;?>
      testtask->status;?>testtask->statusList[$task->status];?>
      testtask->desc;?>desc;?>
      +
      + session->testtaskList ? $this->session->testtaskList : $this->createLink('testtask', 'browse', "productID=$task->product"); + if(!$task->deleted) + { + common::printLink('testtask', 'cases', "taskID=$task->id", $lang->testtask->cases); + common::printLink('testtask', 'linkcase', "taskID=$task->id", $lang->testtask->linkCaseAB); + common::printLink('testtask', 'edit', "taskID=$task->id",$lang->edit); + common::printLink('testtask', 'delete', "taskID=$task->id", $lang->delete, 'hiddenwin'); + echo html::a($browseLink, $lang->goback); + } + ?> +
      + + diff --git a/trunk/module/todo/config.php b/trunk/module/todo/config.php new file mode 100644 index 0000000000..09bc320557 --- /dev/null +++ b/trunk/module/todo/config.php @@ -0,0 +1,14 @@ +todo->batchCreate = 8; + +$config->todo->create->requiredFields = 'name'; +$config->todo->edit->requiredFields = 'name'; +$config->todo->dates->end = 15; +$config->todo->times->begin = 6; +$config->todo->times->end = 23; +$config->todo->times->delta = 10; + +$config->todo->editor->create = array('id' => 'desc', 'tools' => 'simpleTools'); +$config->todo->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools'); + +$config->todo->list->exportFields = 'id, account, date, begin, end, type, idvalue, pri, name, desc, status, private'; diff --git a/trunk/module/todo/control.php b/trunk/module/todo/control.php new file mode 100644 index 0000000000..16350c9f04 --- /dev/null +++ b/trunk/module/todo/control.php @@ -0,0 +1,285 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +class todo extends control +{ + /** + * Construct function, load model of task, bug, my. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('task'); + $this->loadModel('bug'); + $this->loadModel('my')->setMenu(); + } + + /** + * Create a todo. + * + * @param string|date $date + * @param string $account + * @access public + * @return void + */ + public function create($date = 'today', $account = '') + { + if($date == 'today') $date = $this->todo->today(); + if($account == '') $account = $this->app->user->account; + if(!empty($_POST)) + { + $todoID = $this->todo->create($date, $account); + if(dao::isError()) die(js::error(dao::getError())); + $this->loadModel('action')->create('todo', $todoID, 'opened'); + die(js::locate($this->createLink('my', 'todo', "date=" . str_replace('-', '', $this->post->date)), 'parent')); + } + + $header['title'] = $this->lang->my->common . $this->lang->colon . $this->lang->todo->create; + $position[] = $this->lang->todo->create; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->date = strftime("%Y-%m-%d", strtotime($date)); + $this->view->times = $this->todo->buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->time = $this->todo->now(); + $this->display(); + } + + /** + * Batch create todo + * + * @param string $date + * @param string $account + * @access public + * @return void + */ + public function batchCreate($date = 'today', $account = '') + { + if($date == 'today') $date = date(DT_DATE1, time()); + if(!empty($_POST)) + { + $this->todo->batchCreate(); + if(dao::isError()) die(js::error(dao::getError())); + + /* Locate the browser. */ + $date = str_replace('-', '', $this->post->date); + die(js::locate($this->createLink('my', 'todo', "date=$date"), 'parent')); + } + + $header['title'] = $this->lang->my->common . $this->lang->colon . $this->lang->todo->create; + $position[] = $this->lang->todo->create; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->date = (int)$date == 0 ? $date : date('Y-m-d', strtotime($date)); + $this->view->times = $this->todo->buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->time = $this->todo->now(); + + $this->display(); + } + + /** + * Edit a todo. + * + * @param int $todoID + * @access public + * @return void + */ + public function edit($todoID) + { + if(!empty($_POST)) + { + $changes = $this->todo->update($todoID); + if(dao::isError()) die(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('todo', $todoID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + die(js::locate(inlink('view', "todoID=$todoID"), 'parent')); + } + + /* Judge a private todo or not, If private, die. */ + $todo = $this->todo->getById($todoID); + if($todo->private and $this->app->user->account != $todo->account) die('private'); + + $todo->date = strftime("%Y-%m-%d", strtotime($todo->date)); + $header['title'] = $this->lang->my->common . $this->lang->colon . $this->lang->todo->edit; + $position[] = $this->lang->todo->edit; + + $this->view->header = $header; + $this->view->position = $position; + $this->view->times = $this->todo->buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->todo = $todo; + $this->display(); + } + + /** + * View a todo. + * + * @param int $todoID + * @param string $from my|company + * @access public + * @return void + */ + public function view($todoID, $from = 'company') + { + $todo = $this->todo->getById($todoID, true); + if(!$todo) die(js::error($this->lang->notFound) . js::locate('back')); + + /* Save the session. */ + $this->session->set('taskList', $this->app->getURI(true)); + $this->session->set('bugList', $this->app->getURI(true)); + + /* Set menus. */ + $this->lang->todo->menu = $this->lang->user->menu; + $this->lang->todo->menuOrder = $this->lang->user->menuOrder; + $this->loadModel('user')->setMenu($this->user->getPairs(), $todo->account); + $this->lang->set('menugroup.todo', $from); + + $this->view->header->title = "TODO #$todo->id $todo->name"; + $this->view->position[] = $this->lang->todo->view; + $this->view->todo = $todo; + $this->view->times = $this->todo->buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->actions = $this->loadModel('action')->getList('todo', $todoID); + $this->view->from = $from; + + $this->display(); + } + + /** + * Delete a todo. + * + * @param int $todoID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($todoID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm($this->lang->todo->confirmDelete, $this->createLink('todo', 'delete', "todoID=$todoID&confirm=yes")); + exit; + } + else + { + $this->dao->delete()->from(TABLE_TODO)->where('id')->eq($todoID)->exec(); + $this->loadModel('action')->create('todo', $todoID, 'erased'); + die(js::locate($this->session->todoList, 'parent')); + } + } + + /** + * Mark status of a todo. + * + * @param int $todoID + * @param string $status wait|doing|done + * @access public + * @return void + */ + public function mark($todoID, $status) + { + $this->todo->mark($todoID, $status); + $todo = $this->todo->getById($todoID); + if($todo->status == 'done') + { + if($todo->type == 'bug' or $todo->type == 'task') + { + $confirmNote = 'confirm' . ucfirst($todo->type); + $confirmURL = $this->createLink($todo->type, 'view', "id=$todo->idvalue"); + $cancelURL = $this->server->HTTP_REFERER; + die(js::confirm(sprintf($this->lang->todo->$confirmNote, $todo->idvalue), $confirmURL, $cancelURL, 'parent', 'parent')); + } + } + die(js::reload('parent')); + } + + /** + * Import selected todoes to today. + * + * @access public + * @return void + */ + public function import2Today() + { + $todoIDList = $this->post->todoIDList; + $today = $this->todo->today(); + $this->dao->update(TABLE_TODO)->set('date')->eq($today)->where('id')->in($todoIDList)->exec(); + die(js::locate($this->session->todoList)); + } + + /** + * Get data to export + * + * @param string $productID + * @param string $orderBy + * @access public + * @return void + */ + public function export($account, $orderBy) + { + if($_POST) + { + $todoLang = $this->lang->todo; + $todoConfig = $this->config->todo; + + /* Create field lists. */ + $fields = explode(',', $todoConfig->list->exportFields); + foreach($fields as $key => $fieldName) + { + $fieldName = trim($fieldName); + $fields[$fieldName] = isset($todoLang->$fieldName) ? $todoLang->$fieldName : $fieldName; + unset($fields[$key]); + } + unset($fields['idvalue']); + unset($fields['private']); + + /* Get bugs. */ + $todos = $this->dao->select('*')->from(TABLE_TODO)->where($this->session->todoReportCondition)->orderBy($orderBy)->fetchAll('id'); + + /* Get users, bugs, tasks and times. */ + $users = $this->loadModel('user')->getPairs('noletter'); + $bugs = $this->loadModel('bug')->getUserBugPairs($account); + $tasks = $this->loadModel('task')->getUserTaskPairs($account); + $times = $this->todo->buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + + foreach($todos as $todo) + { + /* fill some field with useful value. */ + if(isset($users[$todo->account])) $todo->account = $users[$todo->account]; + if(isset($times[$todo->begin])) $todo->begin = $times[$todo->begin]; + if(isset($times[$todo->end])) $todo->end = $times[$todo->end]; + if($todo->type == 'bug') $todo->name = isset($bugs[$todo->idvalue]) ? $bugs[$todo->idvalue] : ''; + if($todo->type == 'task') $todo->name = isset($tasks[$todo->idvalue]) ? $tasks[$todo->idvalue] : ''; + if(isset($todoLang->typeList->{$todo->type})) $todo->type = $todoLang->typeList->{$todo->type}; + if(isset($todoLang->priList[$todo->pri])) $todo->pri = $todoLang->priList[$todo->pri]; + if(isset($todoLang->statusList[$todo->status])) $todo->status = $todoLang->statusList[$todo->status]; + if($todo->private == 1) $todo->desc = $this->lang->todo->thisIsPrivate; + + /* drop some field that is not needed. */ + unset($todo->company); + unset($todo->idvalue); + unset($todo->private); + } + + $this->post->set('fields', $fields); + $this->post->set('rows', $todos); + $this->fetch('file', 'export2' . $this->post->fileType, $_POST); + } + + $this->display(); + } +} diff --git a/trunk/module/todo/css/batchcreate.css b/trunk/module/todo/css/batchcreate.css new file mode 100755 index 0000000000..58d667d613 --- /dev/null +++ b/trunk/module/todo/css/batchcreate.css @@ -0,0 +1,4 @@ +.half-left{text-align:right} +.half-right{text-align:left} +.nameBox .f-left {margin-left:20px} +.select-2 {width: 75px} diff --git a/trunk/module/todo/js/batchcreate.js b/trunk/module/todo/js/batchcreate.js new file mode 100644 index 0000000000..628839fd7c --- /dev/null +++ b/trunk/module/todo/js/batchcreate.js @@ -0,0 +1,27 @@ +function updateAction(date) +{ + if(date.indexOf('-') != -1) + { + var datearray = date.split("-"); + var date = ''; + for(i=0 ; i + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->todo->common = 'TODO'; +$lang->todo->index = "Index"; +$lang->todo->create = "Create"; +$lang->todo->batchCreate = "Batch create"; +$lang->todo->edit = "Edit"; +$lang->todo->view = "Info"; +$lang->todo->viewAB = "Info"; +$lang->todo->markDone = "Undone"; +$lang->todo->markWait = "Done"; +$lang->todo->markDoing = "Done"; +$lang->todo->mark = "Change status"; +$lang->todo->export = "Export"; +$lang->todo->delete = "Delete"; +$lang->todo->browse = "Browse"; +$lang->todo->import2Today = "Import to today"; +$lang->todo->changeStatus = "Change"; + +$lang->todo->id = 'ID'; +$lang->todo->account = 'Owner'; +$lang->todo->date = 'Date'; +$lang->todo->begin = 'Begin time'; +$lang->todo->beginAB = 'Begin'; +$lang->todo->end = 'End time'; +$lang->todo->endAB = 'End'; +$lang->todo->beginAndEnd = 'Begin and End'; +$lang->todo->type = 'Type'; +$lang->todo->pri = 'Priority'; +$lang->todo->name = 'Name'; +$lang->todo->status = 'Status'; +$lang->todo->desc = 'Desc'; +$lang->todo->private = 'Private'; +$lang->todo->idvalue = 'Task or bug'; + +$lang->todo->week = '(l)'; // date function's param. +$lang->todo->today = 'Today'; +$lang->todo->weekDateList = ''; +$lang->todo->dayInFuture = 'Future'; +$lang->todo->confirmBug = 'This todo linked to bug #%s,chang it also?'; +$lang->todo->confirmTask = 'This todo linked to task #%s,chang it also?'; + +$lang->todo->statusList['wait'] = 'Waiting'; +$lang->todo->statusList['doing'] = 'Doing'; +$lang->todo->statusList['done'] = 'Done'; +//$lang->todo->statusList['cancel'] = '已取消'; +//$lang->todo->statusList['postpone'] = '已延期'; + +$lang->todo->priList[3] = '3'; +$lang->todo->priList[1] = '1'; +$lang->todo->priList[2] = '2'; +$lang->todo->priList[4] = '4'; + +$lang->todo->typeList->custom = 'Custom'; +$lang->todo->typeList->bug = 'Bug'; +$lang->todo->typeList->task = 'Task'; + +$lang->todo->confirmDelete = "Are you sure to delete this todo?"; +$lang->todo->successMarked = "Successfully changed status";; +$lang->todo->thisIsPrivate = 'This is a private todo。:)'; +$lang->todo->lblDisableDate = 'Set time lately'; + +$lang->todo->thisWeekTodos = 'This week'; +$lang->todo->lastWeekTodos = 'Last week'; +$lang->todo->futureTodos = 'Future'; +$lang->todo->allDaysTodos = 'All'; +$lang->todo->allUndone = 'Undone'; +$lang->todo->todayTodos = 'Today'; +$lang->todo->yesterdayTodos = 'Yesterday'; +$lang->todo->lastmonthTodos = 'Last Month'; +$lang->todo->thisseasonTodos = 'This Season'; +$lang->todo->thisyearTodos = 'This Year'; +$lang->todo->thismonthTodos = 'This Month'; +$lang->todo->action->marked = array('main' => '$date, Change status to $extra by $actor。', 'extra' => $lang->todo->statusList); diff --git a/trunk/module/todo/lang/zh-cn.php b/trunk/module/todo/lang/zh-cn.php new file mode 100644 index 0000000000..4957b27040 --- /dev/null +++ b/trunk/module/todo/lang/zh-cn.php @@ -0,0 +1,83 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +$lang->todo->common = 'TODO'; +$lang->todo->index = "todo一览"; +$lang->todo->create = "新增"; +$lang->todo->batchCreate = "批量添加"; +$lang->todo->edit = "更新TODO"; +$lang->todo->view = "TODO详情"; +$lang->todo->viewAB = "详情"; +$lang->todo->markDone = "未完成"; +$lang->todo->markWait = "已完成"; +$lang->todo->markDoing = "已完成"; +$lang->todo->mark = "更改状态"; +$lang->todo->export = "导出"; +$lang->todo->delete = "删除TODO"; +$lang->todo->browse = "浏览TODO"; +$lang->todo->import2Today = "导入到今天"; +$lang->todo->changeStatus = "更改"; + +$lang->todo->id = '编号'; +$lang->todo->account = '所有者'; +$lang->todo->date = '日期'; +$lang->todo->begin = '开始时间'; +$lang->todo->beginAB = '开始'; +$lang->todo->end = '结束时间'; +$lang->todo->endAB = '结束'; +$lang->todo->beginAndEnd = '起止时间'; +$lang->todo->type = '类型'; +$lang->todo->pri = '优先级'; +$lang->todo->name = '名称'; +$lang->todo->status = '状态'; +$lang->todo->desc = '描述'; +$lang->todo->private = '私人事务'; +$lang->todo->idvalue = '任务或Bug'; + +$lang->todo->week = '星期'; +$lang->todo->today = '今天'; +$lang->todo->weekDateList = '一,二,三,四,五,六,天'; +$lang->todo->dayInFuture = '暂不指定'; +$lang->todo->confirmBug = '该Todo关联的是Bug #%s,需要修改它吗?'; +$lang->todo->confirmTask = '该Todo关联的是Task #%s,需要修改它吗?'; + +$lang->todo->statusList['wait'] = '未开始'; +$lang->todo->statusList['doing'] = '进行中'; +$lang->todo->statusList['done'] = '已完成'; +//$lang->todo->statusList['cancel'] = '已取消'; +//$lang->todo->statusList['postpone'] = '已延期'; + +$lang->todo->priList[3] = '一般'; +$lang->todo->priList[1] = '最高'; +$lang->todo->priList[2] = '较高'; +$lang->todo->priList[4] = '最低'; + +$lang->todo->typeList->custom = '自定义'; +$lang->todo->typeList->bug = 'Bug'; +$lang->todo->typeList->task = '项目任务'; + +$lang->todo->confirmDelete = "您确定要删除这个todo吗?"; +$lang->todo->successMarked = "成功切换状态!"; +$lang->todo->thisIsPrivate = '这是一条私人事务。:)'; +$lang->todo->lblDisableDate = '暂时不设定时间'; + +$lang->todo->thisWeekTodos = '本周'; +$lang->todo->lastWeekTodos = '上周'; +$lang->todo->futureTodos = '暂不指定'; +$lang->todo->allDaysTodos = '所有'; +$lang->todo->allUndone = '之前未完'; +$lang->todo->todayTodos = '今日'; +$lang->todo->yesterdayTodos = '昨日'; +$lang->todo->lastmonthTodos = '上月'; +$lang->todo->thisseasonTodos = '本季'; +$lang->todo->thisyearTodos = '本年'; +$lang->todo->thismonthTodos = '本月'; +$lang->todo->action->marked = array('main' => '$date, 由 $actor 标记为$extra。', 'extra' => $lang->todo->statusList); diff --git a/trunk/module/todo/lang/zh-tw.php b/trunk/module/todo/lang/zh-tw.php new file mode 100644 index 0000000000..090ec8e909 --- /dev/null +++ b/trunk/module/todo/lang/zh-tw.php @@ -0,0 +1,83 @@ + + * @package todo + * @version $Id: zh-tw.php 3457 2012-09-01 07:46:30Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +$lang->todo->common = 'TODO'; +$lang->todo->index = "todo一覽"; +$lang->todo->create = "新增"; +$lang->todo->batchCreate = "批量添加"; +$lang->todo->edit = "更新TODO"; +$lang->todo->view = "TODO詳情"; +$lang->todo->viewAB = "詳情"; +$lang->todo->markDone = "未完成"; +$lang->todo->markWait = "已完成"; +$lang->todo->markDoing = "已完成"; +$lang->todo->mark = "更改狀態"; +$lang->todo->export = "導出"; +$lang->todo->delete = "刪除TODO"; +$lang->todo->browse = "瀏覽TODO"; +$lang->todo->import2Today = "導入到今天"; +$lang->todo->changeStatus = "更改"; + +$lang->todo->id = '編號'; +$lang->todo->account = '所有者'; +$lang->todo->date = '日期'; +$lang->todo->begin = '開始時間'; +$lang->todo->beginAB = '開始'; +$lang->todo->end = '結束時間'; +$lang->todo->endAB = '結束'; +$lang->todo->beginAndEnd = '起止時間'; +$lang->todo->type = '類型'; +$lang->todo->pri = '優先順序'; +$lang->todo->name = '名稱'; +$lang->todo->status = '狀態'; +$lang->todo->desc = '描述'; +$lang->todo->private = '私人事務'; +$lang->todo->idvalue = '任務或Bug'; + +$lang->todo->week = '星期'; +$lang->todo->today = '今天'; +$lang->todo->weekDateList = '一,二,三,四,五,六,天'; +$lang->todo->dayInFuture = '暫不指定'; +$lang->todo->confirmBug = '該Todo關聯的是Bug #%s,需要修改它嗎?'; +$lang->todo->confirmTask = '該Todo關聯的是Task #%s,需要修改它嗎?'; + +$lang->todo->statusList['wait'] = '未開始'; +$lang->todo->statusList['doing'] = '進行中'; +$lang->todo->statusList['done'] = '已完成'; +//$lang->todo->statusList['cancel'] = '已取消'; +//$lang->todo->statusList['postpone'] = '已延期'; + +$lang->todo->priList[3] = '一般'; +$lang->todo->priList[1] = '最高'; +$lang->todo->priList[2] = '較高'; +$lang->todo->priList[4] = '最低'; + +$lang->todo->typeList->custom = '自定義'; +$lang->todo->typeList->bug = 'Bug'; +$lang->todo->typeList->task = '項目任務'; + +$lang->todo->confirmDelete = "您確定要刪除這個todo嗎?"; +$lang->todo->successMarked = "成功切換狀態!"; +$lang->todo->thisIsPrivate = '這是一條私人事務。:)'; +$lang->todo->lblDisableDate = '暫時不設定時間'; + +$lang->todo->thisWeekTodos = '本週'; +$lang->todo->lastWeekTodos = '上周'; +$lang->todo->futureTodos = '暫不指定'; +$lang->todo->allDaysTodos = '所有'; +$lang->todo->allUndone = '之前未完'; +$lang->todo->todayTodos = '今日'; +$lang->todo->yesterdayTodos = '昨日'; +$lang->todo->lastmonthTodos = '上月'; +$lang->todo->thisseasonTodos = '本季'; +$lang->todo->thisyearTodos = '本年'; +$lang->todo->thismonthTodos = '本月'; +$lang->todo->action->marked = array('main' => '$date, 由 $actor 標記為$extra。', 'extra' => $lang->todo->statusList); diff --git a/trunk/module/todo/model.php b/trunk/module/todo/model.php new file mode 100644 index 0000000000..7775b2cf1c --- /dev/null +++ b/trunk/module/todo/model.php @@ -0,0 +1,523 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +?> +add('account', $this->app->user->account) + ->add('idvalue', 0) + ->specialChars('type,name') + ->cleanInt('date, pri, begin, end, private') + ->setIF($this->post->type != 'custom', 'name', '') + ->setIF($this->post->type == 'bug' and $this->post->bug, 'idvalue', $this->post->bug) + ->setIF($this->post->type == 'task' and $this->post->task, 'idvalue', $this->post->task) + ->setIF($this->post->date == false, 'date', '2030-01-01') + ->setIF($this->post->begin == false, 'begin', '2400') + ->setIF($this->post->end == false, 'end', '2400') + ->remove('bug, task') + ->get(); + $this->dao->insert(TABLE_TODO)->data($todo) + ->autoCheck() + ->checkIF($todo->type == 'custom', $this->config->todo->create->requiredFields, 'notempty') + ->checkIF($todo->type == 'bug' and $todo->idvalue == 0, 'idvalue', 'notempty') + ->checkIF($todo->type == 'task' and $todo->idvalue == 0, 'idvalue', 'notempty') + ->exec(); + return $this->dao->lastInsertID(); + } + + /** + * Create batch todo + * + * @access public + * @return void + */ + public function batchCreate() + { + $todos = fixer::input('post')->cleanInt('date')->get(); + for($i = 0; $i < $this->config->todo->batchCreate; $i++) + { + if($todos->names[$i] != '' || isset($todos->bugs[$i + 1]) || isset($todos->tasks[$i + 1])) + { + $todo->account = $this->app->user->account; + if($this->post->date == false) + { + $todo->date = '2030-01-01'; + } + else + { + $todo->date = $this->post->date; + } + $todo->type = $todos->types[$i]; + $todo->pri = $todos->pris[$i]; + $todo->name = isset($todos->names[$i]) ? $todos->names[$i] : ''; + $todo->desc = $todos->descs[$i]; + $todo->begin = $todos->begins[$i]; + $todo->end = $todos->ends[$i]; + $todo->status = "wait"; + $todo->private = 0; + $todo->idvalue = 0; + if($todo->type == 'bug') $todo->idvalue = isset($todos->bugs[$i + 1]) ? $todos->bugs[$i + 1] : 0; + if($todo->type == 'task') $todo->idvalue = isset($todos->tasks[$i + 1]) ? $todos->tasks[$i + 1] : 0; + + $this->dao->insert(TABLE_TODO)->data($todo)->autoCheck()->exec(); + if(dao::isError()) + { + echo js::error(dao::getError()); + die(js::reload('parent')); + } + } + else + { + unset($todos->types[$i]); + unset($todos->pris[$i]); + unset($todos->names[$i]); + unset($todos->descs[$i]); + unset($todos->begins[$i]); + unset($todos->ends[$i]); + } + } + } + + /** + * update a todo. + * + * @param int $todoID + * @access public + * @return void + */ + public function update($todoID) + { + $oldTodo = $this->getById($todoID); + if($oldTodo->type != 'custom') $oldTodo->name = ''; + $todo = fixer::input('post') + ->cleanInt('date, pri, begin, end, private') + ->specialChars('type,name') + ->setIF($this->post->type != 'custom', 'name', '') + ->setIF($this->post->date == false, 'date', '2030-01-01') + ->setIF($this->post->begin == false, 'begin', '2400') + ->setIF($this->post->end == false, 'end', '2400') + ->setDefault('private', 0) + ->get(); + $this->dao->update(TABLE_TODO)->data($todo) + ->autoCheck() + ->checkIF($todo->type == 'custom', $this->config->todo->edit->requiredFields, 'notempty')->where('id')->eq($todoID) + ->exec(); + if(!dao::isError()) return common::createChanges($oldTodo, $todo); + } + + /** + * Change the status of a todo. + * + * @param string $todoID + * @param string $status + * @access public + * @return void + */ + public function mark($todoID, $status) + { + $status = ($status == 'done') ? 'wait' : 'done'; + $this->dao->update(TABLE_TODO)->set('status')->eq($status)->where('id')->eq((int)$todoID)->exec(); + $this->loadModel('action')->create('todo', $todoID, 'marked', '', $status); + return; + } + + /** + * Get info of a todo. + * + * @param int $todoID + * @param bool $setImgSize + * @access public + * @return object|bool + */ + public function getById($todoID, $setImgSize = false) + { + $todo = $this->dao->findById((int)$todoID)->from(TABLE_TODO)->fetch(); + if(!$todo) return false; + if($setImgSize) $todo->desc = $this->loadModel('file')->setImgSize($todo->desc); + if($todo->type == 'task') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_TASK)->fetch('name'); + if($todo->type == 'bug') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_BUG)->fetch('title'); + $todo->date = str_replace('-', '', $todo->date); + return $todo; + } + + /** + * Get todo list of a user. + * + * @param date $date + * @param string $account + * @param string $status all|today|thisweek|lastweek|before, or a date. + * @param int $limit + * @access public + * @return void + */ + public function getList($date = 'today', $account = '', $status = 'all', $limit = 0, $pager = null) + { + $todos = array(); + if($date == 'today') + { + $begin = $this->today(); + $end = $begin; + } + elseif($date == 'yesterday') + { + $begin = $this->yesterday(); + $end = $begin; + } + elseif($date == 'thisweek') + { + extract($this->getThisWeek()); + } + elseif($date == 'lastweek') + { + extract($this->getLastWeek()); + } + elseif($date == 'thismonth') + { + extract($this->getThisMonth()); + } + elseif($date == 'lastmonth') + { + extract($this->getLastMonth()); + } + elseif($date == 'thisseason') + { + extract($this->getThisSeason()); + } + elseif($date == 'thisyear') + { + extract($this->getThisYear()); + } + elseif($date == 'future') + { + $begin = '2030-01-01'; + $end = $begin; + } + elseif($date == 'all') + { + $begin = '1970-01-01'; + $end = '2109-01-01'; + } + elseif($date == 'before') + { + $begin = '1970-01-01'; + $end = $this->yesterday(); + } + else + { + $begin = $end = $date; + } + + if($account == '') $account = $this->app->user->account; + + $stmt = $this->dao->select('*')->from(TABLE_TODO) + ->where('account')->eq($account) + ->andWhere("date >= '$begin'") + ->andWhere("date <= '$end'") + ->beginIF($status != 'all' and $status != 'undone')->andWhere('status')->in($status)->fi() + ->beginIF($status == 'undone')->andWhere('status')->ne('done')->fi() + ->orderBy('date, status, begin') + ->beginIF($limit > 0)->limit($limit)->fi() + ->page($pager) + ->query(); + + /* Set session. */ + $sql = explode('WHERE', $this->dao->get()); + $sql = explode('ORDER', $sql[1]); + $this->session->set('todoReportCondition', $sql[0]); + + while($todo = $stmt->fetch()) + { + if($todo->type == 'task') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_TASK)->fetch('name'); + if($todo->type == 'bug') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_BUG)->fetch('title'); + $todo->begin = $this->formatTime($todo->begin); + $todo->end = $this->formatTime($todo->end); + + /* If is private, change the title to private. */ + if($todo->private and $this->app->user->account != $todo->account) $todo->name = $this->lang->todo->thisIsPrivate; + $todos[] = $todo; + } + return $todos; + } + + /** + * Build date list, for selection use. + * + * @param int $before + * @param int $after + * @access public + * @return void + */ + public function buildDateList($before = 7, $after = 7) + { + $today = strtotime($this->today()); + $delta = 60 * 60 * 24; + $dates = array(); + $weekList = range(1, 7); + $weekDateList = explode(',', $this->lang->todo->weekDateList); + for($i = -1 * $before; $i <= $after; $i ++) + { + $time = $today + $i * $delta; + $label = date(DT_DATE1, $time); + if($i == 0) + { + $label .= " ({$this->lang->todo->today})"; + } + else + { + if($this->cookie->lang == 'zh-cn' or $this->cookie->lang == 'zh-tw') + { + $label .= str_replace($weekList, $weekDateList, date(" ({$this->lang->todo->week}N)", $time)); + } + else + { + $label .= date($this->lang->todo->week, $time); + } + } + $date = date(DT_DATE2, $time); + $dates[$date] = $label; + } + $dates[self::DAY_IN_FUTURE] = $this->lang->todo->dayInFuture; + return $dates; + } + + /** + * Build hour time list. + * + * @param int $begin + * @param int $end + * @param int $delta + * @access public + * @return array + */ + public function buildTimeList($begin, $end, $delta) + { + $times = array(); + for($hour = $begin; $hour <= $end; $hour ++) + { + for($minutes = 0; $minutes < 60; $minutes += $delta) + { + $time = sprintf('%02d%02d', $hour, $minutes); + $label = sprintf('%02d:%02d', $hour, $minutes); + $times[$time] = $label; + } + } + return $times; + } + + /** + * Get today. + * + * @access public + * @return date + */ + public function today() + { + return date(DT_DATE2, time()); + } + + /** + * Get yesterday + * + * @access public + * @return date + */ + public function yesterday() + { + return date(DT_DATE1, strtotime('yesterday')); + } + + /** + * Get tomorrow. + * + * @access public + * @return date + */ + public function tomorrow() + { + return date(DT_DATE1, strtotime('tomorrow')); + } + + /** + * Get the day before yesterday. + * + * @access public + * @return date + */ + public function twoDaysAgo() + { + return date(DT_DATE1, strtotime('-2 days')); + } + + /** + * Get now time period. + * + * @param int $delta + * @access public + * @return string the current time period, like 0915 + */ + public function now($delta = 10) + { + $range = range($delta, 60 - $delta, $delta); + $hour = date('H', time()); + $minute = date('i', time()); + + if($minute > 60 - $delta) + { + $hour += 1; + $minute = 00; + } + else + { + for($i = 0; $i < $delta; $i ++) + { + if(in_array($minute + $i, $range)) + { + $minute = $minute + $i; + break; + } + } + } + + return sprintf('%02d%02d', $hour, $minute); + } + + /** + * Format time 0915 to 09:15 + * + * @param string $time + * @access public + * @return string + */ + public function formatTime($time) + { + if(strlen($time) != 4 or $time == '2400') return ''; + return substr($time, 0, 2) . ':' . substr($time, 2, 2); + } + + /** + * Get the begin and end date of this week. + * + * @access public + * @return array + */ + public function getThisWeek() + { + $baseTime = $this->getMiddleOfThisWeek(); + $begin = date(DT_DATE1, strtotime('last monday', $baseTime)); + $end = date(DT_DATE1, strtotime('next sunday', $baseTime)); + return array('begin' => $begin, 'end' => $end); + } + + /** + * Get the begin and end date of last week. + * + * @access public + * @return array + */ + public function getLastWeek() + { + $baseTime = $this->getMiddleOfLastWeek(); + $begin = date(DT_DATE1, strtotime('last monday', $baseTime)); + $end = date(DT_DATE1, strtotime('next sunday', $baseTime)); + return array('begin' => $begin, 'end' => $end); + } + + /** + * Get the time at the middle of this week. + * + * If today in week is 1, move it one day in future. Else is 7, move it back one day. To keep the time geted in this week. + * + * @access public + * @return time + */ + public function getMiddleOfThisWeek() + { + $baseTime = time(); + $weekDay = date('N'); + if($weekDay == 1) $baseTime = time() + 86400; + if($weekDay == 7) $baseTime = time() - 86400; + return $baseTime; + } + + /** + * Get middle of last week + * + * @access public + * @return time + */ + public function getMiddleOfLastWeek() + { + $baseTime = time(); + $weekDay = date('N'); + $baseTime = time() - 86400 * 7; + if($weekDay == 1) $baseTime = time() - 86400 * 4; // Make sure is last thursday. + if($weekDay == 7) $baseTime = time() - 86400 * 10; // Make sure is last thursday. + return $baseTime; + } + + /** + * Get this month begin and end time + * + * @access public + * @return array + */ + public function getThisMonth() + { + $begin = date('Y-m'); + $end = date('Y-m', strtotime('next month')); + return array('begin' => $begin, 'end' => $end); + } + + /** + * Get last month begin and end time + * + * @access public + * @return array + */ + public function getLastMonth() + { + $begin = date('Y-m', strtotime('last month')); + $end = date('Y-m', strtotime('this month')); + return array('begin' => $begin, 'end' => $end); + } + + public function getThisSeason() + { + $getMonthDays = date("t", mktime(0,0,0,date('n')-(date('n')-1)%3,1,date("Y"))); + $begin = date('Y-m-d H:i:s', mktime(0,0,0,date('n')-(date('n')-1)%3,1,date("Y"))); + $end = date('Y-m-d H:i:s', mktime(23,59,59,date('n')+(date('n')-1)%3,$getMonthDays,date('Y'))); + return array('begin' => $begin, 'end' => $end); + } + + public function getThisYear() + { + $begin = date(DT_DATE1, strtotime('1/1 this year')); + $end = date(DT_DATE1, strtotime('1/1 next year -1 day')); + return array('begin' => $begin, 'end' => $end); + } +} + + + diff --git a/trunk/module/todo/view/batchcreate.html.php b/trunk/module/todo/view/batchcreate.html.php new file mode 100755 index 0000000000..9e79a1acb6 --- /dev/null +++ b/trunk/module/todo/view/batchcreate.html.php @@ -0,0 +1,54 @@ + + * @package todo + * @version $Id: create.html.php 2741 2012-04-07 07:24:21Z areyou123456 $ + * @link http://www.zentao.net + */ +?> + + +
      + + + + + + + + + + + + + todo->batchCreate; $i++):?> + + + + + + + + + +
      todo->batchCreate . $lang->colon . html::input('date', $date, "class='select-2 date' onchange='updateAction(this.value)'");?> + todo->futureTodos;?>
      idAB;?>todo->type;?>todo->pri;?>todo->name;?>todo->desc;?>todo->beginAndEnd;?>
      todo->typeList, '', "onchange=loadList(this.value,$i+1) class='select-1'");?>todo->priList, $pri, 'class=select-1');?> + +
      +
      + + +
      +
      + + diff --git a/trunk/module/todo/view/create.html.php b/trunk/module/todo/view/create.html.php new file mode 100644 index 0000000000..965c3c2166 --- /dev/null +++ b/trunk/module/todo/view/create.html.php @@ -0,0 +1,71 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      todo->create;?>
      todo->date;?> + todo->futureTodos;?>
      todo->type;?>todo->typeList, '', 'onchange=loadList(this.value); class=select-3');?> +
      todo->pri;?>todo->priList, '', 'class=select-3');?>
      todo->name;?> + +
      +
      todo->desc;?> +
      todo->status;?>todo->statusList, '', 'class=select-3');?>
      todo->beginAndEnd;?> + + todo->lblDisableDate;?> +
      todo->private;?>
      + +
      +
      + + diff --git a/trunk/module/todo/view/edit.html.php b/trunk/module/todo/view/edit.html.php new file mode 100644 index 0000000000..ff49369ac5 --- /dev/null +++ b/trunk/module/todo/view/edit.html.php @@ -0,0 +1,69 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      todo->edit;?>
      todo->date;?>date, "class='select-3 date'");?> + todo->futureTodos;?>
      todo->type;?>todo->typeList->{$todo->type};?>
      todo->pri;?>todo->priList, $todo->pri, 'class=select-3');?>
      todo->name;?>
      + type != 'custom' ? 'readonly' : ''; + echo html::input('name', $todo->name, "$readType class=text-1"); + ?> +
      +
      todo->desc;?>desc), "rows=8 class=area-1");?>
      todo->status;?>todo->statusList, $todo->status, 'class=select-3');?>
      todo->beginAndEnd;?> + begin, 'onchange=selectNext(); class=select-2') . html::select('end', $times, $todo->end, 'class=select-2');?> + begin == 2400) echo 'checked';?> >todo->lblDisableDate;?> +
      todo->private;?>private) echo 'checked';?>>
      + +
      +
      + + diff --git a/trunk/module/todo/view/export.html.php b/trunk/module/todo/view/export.html.php new file mode 100755 index 0000000000..6bc1e78954 --- /dev/null +++ b/trunk/module/todo/view/export.html.php @@ -0,0 +1,13 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +?> + diff --git a/trunk/module/todo/view/footer.html.php b/trunk/module/todo/view/footer.html.php new file mode 100644 index 0000000000..2c37a9055f --- /dev/null +++ b/trunk/module/todo/view/footer.html.php @@ -0,0 +1,93 @@ + + diff --git a/trunk/module/todo/view/view.html.php b/trunk/module/todo/view/view.html.php new file mode 100644 index 0000000000..7dc1b80b6b --- /dev/null +++ b/trunk/module/todo/view/view.html.php @@ -0,0 +1,87 @@ + + * @package todo + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +private or ($todo->private and $todo->account == $app->user->account)):?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      TODO #id . ' ' . $todo->name;?>
      todo->account;?>account;?>
      todo->date;?>date == TODOModel::DAY_IN_FUTURE ? $lang->todo->dayInFuture : date(DT_DATE1, strtotime($todo->date)));?>
      todo->type;?>todo->typeList->{$todo->type};?>
      todo->pri;?>todo->priList[$todo->pri];?>
      todo->name;?> + type == 'bug') echo html::a($this->createLink('bug', 'view', "id={$todo->idvalue}"), $todo->name); + if($todo->type == 'task') echo html::a($this->createLink('task', 'view', "id={$todo->idvalue}"), $todo->name); + if($todo->type == 'custom') echo $todo->name; + ?> +
      todo->desc;?>desc;?>
      todo->status;?>todo->statusList[$todo->status];?>
      todo->beginAndEnd;?> + begin])) echo $times[$todo->begin]; + if(isset($times[$todo->end])) echo ' ~ ' . $times[$todo->end]; + ?> +
      +
      + session->todoList) + { + $browseLink = $this->session->todoList; + } + elseif($todo->account == $app->user->account) + { + $browseLink = $this->createLink('my', 'todo'); + } + else + { + $browseLink = $this->createLink('user', 'todo', "account=$todo->account"); + } + if($todo->account == $app->user->account) + { + common::printLink('todo', 'edit', "todoID=$todo->id", $lang->edit); + common::printLink('todo', 'delete', "todoID=$todo->id", $lang->delete, 'hiddenwin'); + } + echo html::a($browseLink, $lang->goback); + ?> +
      + + +todo->thisIsPrivate;?> + + diff --git a/trunk/module/tree/control.php b/trunk/module/tree/control.php new file mode 100644 index 0000000000..445fe5fb78 --- /dev/null +++ b/trunk/module/tree/control.php @@ -0,0 +1,280 @@ + + * @package tree + * @version $Id$ + * @link http://www.zentao.net + */ +class tree extends control +{ + const NEW_CHILD_COUNT = 5; + + /** + * Module browse. + * + * @param int $rootID + * @param string $viewType story|bug|case|doc + * @param int $currentModuleID + * @access public + * @return void + */ + public function browse($rootID, $viewType, $currentModuleID = 0) + { + /* According to the type, set the module root and modules. */ + if(strpos('story|bug|case', $viewType) !== false) + { + $product = $this->loadModel('product')->getById($rootID); + $this->view->root = $product; + $this->view->productModules = $this->tree->getOptionMenu($rootID, 'story'); + } + elseif($viewType == 'task') + { + $project = $this->loadModel('project')->getById($rootID); + $this->view->root = $project; + $this->view->projectModules = $this->tree->getOptionMenu($rootID, 'task'); + } + /* The viewType is doc. */ + elseif(strpos($viewType, 'doc') !== false) + { + $this->loadModel('doc'); + if($rootID == 'product' or $rootID == 'project') + { + $viewType = $rootID . 'doc'; + $lib->id = $rootID; + $lib->name = $this->lang->doc->systemLibs[$rootID]; + $this->view->root = $lib; + } + else + { + $viewType = 'customdoc'; + $lib = $this->loadModel('doc')->getLibById($rootID); + $this->view->root = $lib; + } + } + + if($viewType == 'story') + { + $this->lang->set('menugroup.tree', 'product'); + $this->product->setMenu($this->product->getPairs(), $rootID, 'story'); + $this->lang->tree->menu = $this->lang->product->menu; + $this->lang->tree->menuOrder = $this->lang->product->menuOrder; + + $products = $this->product->getPairs(); + unset($products[$rootID]); + $currentProduct = key($products); + + $this->view->allProduct = $products; + $this->view->currentProduct = $currentProduct; + $this->view->productModules = $this->tree->getOptionMenu($currentProduct, 'story'); + + $header['title'] = $product->name . $this->lang->colon . $this->lang->tree->manageProduct; + $position[] = html::a($this->createLink('product', 'browse', "product=$rootID"), $product->name); + $position[] = $this->lang->tree->manageProduct; + } + elseif($viewType == 'task') + { + $this->lang->set('menugroup.tree', 'project'); + $this->project->setMenu($this->project->getPairs(), $rootID, 'task'); + $this->lang->tree->menu = $this->lang->project->menu; + $this->lang->tree->menuOrder = $this->lang->project->menuOrder; + + $projects = $this->project->getPairs(); + unset($projects[$rootID]); + $currentProject = key($projects); + + $this->view->allProject = $projects; + $this->view->currentProject = $currentProject; + $this->view->projectModules = $this->tree->getOptionMenu($currentProject, 'task'); + + $header['title'] = $project->name . $this->lang->colon . $this->lang->tree->manageProject; + $position[] = html::a($this->createLink('project', 'task', "projectID=$rootID"), $project->name); + $position[] = $this->lang->tree->manageProject; + } + elseif($viewType == 'bug') + { + $this->loadModel('bug')->setMenu($this->product->getPairs(), $rootID); + $this->lang->tree->menu = $this->lang->bug->menu; + $this->lang->tree->menuOrder = $this->lang->bug->menuOrder; + $this->lang->set('menugroup.tree', 'qa'); + + $header['title'] = $product->name . $this->lang->colon . $this->lang->tree->manageBug; + $position[] = html::a($this->createLink('bug', 'browse', "product=$rootID"), $product->name); + $position[] = $this->lang->tree->manageBug; + } + elseif($viewType == 'case') + { + $this->loadModel('testcase')->setMenu($this->product->getPairs(), $rootID); + $this->lang->tree->menu = $this->lang->testcase->menu; + $this->lang->tree->menuOrder = $this->lang->testcase->menuOrder; + $this->lang->set('menugroup.tree', 'qa'); + + $header['title'] = $product->name . $this->lang->colon . $this->lang->tree->manageCase; + $position[] = html::a($this->createLink('testcase', 'browse', "product=$rootID"), $product->name); + $position[] = $this->lang->tree->manageCase; + } + elseif(strpos($viewType, 'doc') !== false) + { + $this->doc->setMenu($this->doc->getLibs(), $rootID, 'doc'); + $this->lang->tree->menu = $this->lang->doc->menu; + $this->lang->tree->menuOrder = $this->lang->doc->menuOrder; + $this->lang->set('menugroup.tree', 'doc'); + + $header['title'] = $lib->name . $this->lang->colon . $this->lang->tree->manageCustomDoc; + $position[] = html::a($this->createLink('doc', 'browse', "libID=$rootID"), $lib->name); + $position[] = $this->lang->tree->manageCustomDoc; + } + + $parentModules = $this->tree->getParents($currentModuleID); + $this->view->header = $header; + $this->view->position = $position; + $this->view->rootID = $rootID; + $this->view->viewType = $viewType; + $this->view->modules = $this->tree->getTreeMenu($rootID, $viewType, $rooteModuleID = 0, array('treeModel', 'createManageLink')); + $this->view->sons = $this->tree->getSons($rootID, $currentModuleID, $viewType); + $this->view->currentModuleID = $currentModuleID; + $this->view->parentModules = $parentModules; + $this->display(); + } + + /** + * Edit a module. + * + * @param int $moduleID + * @access public + * @return void + */ + public function edit($moduleID) + { + if(!empty($_POST)) + { + $this->tree->update($moduleID); + echo js::alert($this->lang->tree->successSave); + die(js::reload('parent')); + } + $module = $this->tree->getById($moduleID); + if($module->owner == null) + { + $module->owner = $this->loadModel('product')->getById($module->root)->QM; + } + $this->view->module = $module; + $this->view->optionMenu = $this->tree->getOptionMenu($this->view->module->root, $this->view->module->type); + $this->view->users = $this->loadModel('user')->getPairs('noclosed'); + + /* Remove self and childs from the $optionMenu. Because it's parent can't be self or childs. */ + $childs = $this->tree->getAllChildId($moduleID); + foreach($childs as $childModuleID) unset($this->view->optionMenu[$childModuleID]); + + die($this->display()); + } + + /** + * Fix path, grades. + * + * @param string $root + * @param string $type + * @access public + * @return void + */ + public function fix($root, $type) + { + $this->tree->fixModulePath($root, $type); + die(js::alert($this->lang->tree->successFixed) . js::reload('parent')); + } + + /** + * Update modules' orders. + * + * @access public + * @return void + */ + public function updateOrder() + { + if(!empty($_POST)) + { + $this->tree->updateOrder($_POST['orders']); + die(js::reload('parent')); + } + } + + /** + * Manage child modules. + * + * @param int $rootID + * @param string $viewType + * @access public + * @return void + */ + public function manageChild($rootID, $viewType) + { + if(!empty($_POST)) + { + $this->tree->manageChild($rootID, $viewType, $_POST['parentModuleID'], $_POST['modules']); + die(js::reload('parent')); + } + } + + /** + * Delete a module. + * + * @param int $rootID + * @param int $moduleID + * @param string $confirm yes|no + * @access public + * @return void + */ + public function delete($rootID, $moduleID, $confirm = 'no') + { + if($confirm == 'no') + { + echo js::confirm($this->lang->tree->confirmDelete, $this->createLink('tree', 'delete', "rootID=$rootID&moduleID=$moduleID&confirm=yes")); + exit; + } + else + { + $this->tree->delete($moduleID); + die(js::reload('parent')); + } + } + + /** + * AJAX: Get the option menu of modules. + * + * @param int $rootID + * @param string $viewType + * @param int $rootModuleID + * @access public + * @return string the html select string. + */ + public function ajaxGetOptionMenu($rootID, $viewType = 'story', $rootModuleID = 0, $returnType = 'html') + { + + $this->view->productModules = $this->tree->getOptionMenu($rootID, 'story'); + $optionMenu = $this->tree->getOptionMenu($rootID, $viewType, $rootModuleID); + if($returnType == 'html') die( html::select("module", $optionMenu, '', 'onchange=setAssignedTo()')); + if($returnType == 'json') die(json_encode($optionMenu)); + } + + /** + * AJAX: get a module's son modules. + * + * @param int $moduleID + * @param int $rootID + * @access public + * @return string json_encoded modules. + */ + public function ajaxGetSonModules($moduleID, $rootID = 0) + { + if($moduleID) die(json_encode($this->dao->findByParent($moduleID)->from(TABLE_MODULE)->fetchPairs('id', 'name'))); + $modules = $this->dao->select('id, name')->from(TABLE_MODULE) + ->where('root')->eq($rootID) + ->andWhere('parent')->eq('0') + ->andWhere('type')->eq('story') + ->fetchPairs(); + foreach($modules as $key => $name) $modules[$key] = str_replace(" "," ","$name"); + die(json_encode($modules)); + } +} diff --git a/trunk/module/tree/css/edit.css b/trunk/module/tree/css/edit.css new file mode 100644 index 0000000000..0037a851da --- /dev/null +++ b/trunk/module/tree/css/edit.css @@ -0,0 +1 @@ +body{background:white; overflow:none} diff --git a/trunk/module/tree/js/browse.js b/trunk/module/tree/js/browse.js new file mode 100644 index 0000000000..f7442fe6b9 --- /dev/null +++ b/trunk/module/tree/js/browse.js @@ -0,0 +1,58 @@ +function syncModule(rootID) +{ + link = createLink('tree', 'ajaxGetSonModules', 'moduleID=' + $('#productModule').val() + '&rootID=' + rootID); + $.getJSON(link, function(modules) + { + $('.helplink').addClass('hidden'); + $.each(modules, function(key, value) + { + moduleName = value; + $('.text-3').each(function() + { + if(this.value == moduleName) modules[key] = null; + if(!this.value) $(this).parent().addClass('hidden'); + }) + }); + $.each(modules, function(key, value) + { + if(value) $('#sonModule').append("
      "); + }) + }) +} + +function syncProduct(obj) +{ + link = createLink('tree', 'ajaxGetOptionMenu', 'rootID=' + obj.value + "&viewType=story&rootModuleID=0&returnType=json"); + $.getJSON(link, function(modules) + { + $('.helplink').addClass('hidden'); + $('#productModule').empty(); + $.each(modules, function(key, value) + { + $('#productModule').append('