LyWempgB!2g@A$kvq0syPfPsQf2L^+Oh>41A00@hZkdcy46%3P?
zn3;+p3!0vvpm8VV6b-w-z>pIJ!Ntab5X8sJ
z%y9t#&C$|k&(qe|UC#yE+yw*Q;NJ@4c30@#yF!59zkmY^9!$8f;lqBb
zbz2;+u{d($&Pg`6PMy2U@#4{=ckiA)ef|0Y{5No*L4yeqGGzD=qQr?5D_#VVu{KAJ
zA3uf^NwTC#6)A(WZ29tKOqn!k+FYKqIZvKGql*YieW-P!Mzbf~&a^wzr{JGT9Z$Zz
z`Sa-0t1mAB*OJBX<9BmRzy1Ko`1326%)h^Z$^Zs<*? 0) {
+ Sabel::init();
+ Sabel_Bus::create()->run(new Config_Bus());
+ Sabel::shutdown();
+} else {
+ Sabel_Bus::create()->run(new Config_Bus());
+}
diff --git a/generator/skeleton/en/tasks/Fixture.php b/generator/skeleton/en/tasks/Fixture.php
new file mode 100755
index 0000000..114ae3f
--- /dev/null
+++ b/generator/skeleton/en/tasks/Fixture.php
@@ -0,0 +1,183 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Fixture extends Sabel_Sakle_Task
+{
+ public function run()
+ {
+ if (count($this->arguments) < 2) {
+ $this->usage();
+ exit;
+ }
+
+ $method = $this->getFixtureMethod();
+ $this->defineEnvironment($this->arguments[0]);
+ Sabel_Db_Config::initialize(new Config_Database());
+
+ if (Sabel_Console::hasOption("export", $this->arguments)) {
+ unset($this->arguments[array_search("--export", $this->arguments, true)]);
+ return $this->export("fixture");
+ } elseif (Sabel_Console::hasOption("export-csv", $this->arguments)) {
+ $dir = Sabel_Console::getOption("export-csv", $this->arguments);
+ if ($dir === null) $dir = RUN_BASE . DS . "data";
+ return $this->export("csv", $dir);
+ }
+
+ $fixtureName = $this->arguments[1];
+
+ if ($fixtureName === "all") {
+ foreach (scandir(FIXTURE_DIR) as $item) {
+ if ($item === "." || $item === "..") continue;
+ Sabel::fileUsing(FIXTURE_DIR . DS . $item, true);
+ $className = "Fixture_" . substr($item, 0, strlen($item) - 4);
+ $instance = new $className();
+ $instance->initialize();
+ $instance->$method();
+ }
+ } else {
+ $filePath = FIXTURE_DIR . DS . $fixtureName . ".php";
+ if (Sabel::fileUsing($filePath, true)) {
+ $className = "Fixture_" . $fixtureName;
+ $instance = new $className();
+ $instance->initialize();
+ $instance->$method();
+ $this->success(ucfirst($method) . " " . $fixtureName);
+ } else {
+ $this->error("no such fixture file. '{$filePath}'");
+ }
+ }
+ }
+
+ protected function getFixtureMethod()
+ {
+ $method = "upFixture";
+ $arguments = $this->arguments;
+
+ if (Sabel_Console::hasOption("up", $arguments)) {
+ $index = array_search("--up", $arguments, true);
+ unset($arguments[$index]);
+ $arguments = array_values($arguments);
+ }
+
+ if (Sabel_Console::hasOption("down", $arguments)) {
+ $index = array_search("--down", $arguments, true);
+ unset($arguments[$index]);
+ $arguments = array_values($arguments);
+ $method = "downFixture";
+ }
+
+ $this->arguments = $arguments;
+
+ return $method;
+ }
+
+ public function usage()
+ {
+ echo "Usage: sakle Fixture [OPTION] ENVIRONMENT FIXTURE_NAME " . PHP_EOL;
+ echo PHP_EOL;
+ echo " ENVIRONMENT: production | test | development" . PHP_EOL;
+ echo " FIXTURE_NAME: fixture name or 'all'" . PHP_EOL;
+ echo PHP_EOL;
+ echo " OPTION:" . PHP_EOL;
+ echo " --up up fixture(default)" . PHP_EOL;
+ echo " --down down fixture" . PHP_EOL;
+ echo PHP_EOL;
+ echo "Example: sakle Fixture development User" . PHP_EOL;
+ echo PHP_EOL;
+ }
+
+ protected function export($type, $dir = null)
+ {
+ unset($this->arguments[0]);
+
+ if ($type === "csv") {
+ if (DS === "\\") { // win
+ if (preg_match('/^[a-z]:\\\\/i', $dir) === 0) { // relative path
+ $dir = RUN_BASE . DS . $dir;
+ }
+ } else {
+ if ($dir{0} !== "/") { // relative path
+ $dir = RUN_BASE . DS . $dir;
+ }
+ }
+
+ if (!is_dir($dir)) {
+ $this->error("'{$dir}' is not directory.");
+ exit;
+ }
+
+ $this->export_cvs($dir);
+ } else {
+ $this->export_fixture();
+ }
+ }
+
+ protected function export_cvs($dir)
+ {
+ foreach ($this->arguments as $mdlName) {
+ $fp = fopen($dir . DS . "{$mdlName}.csv", "w");
+ foreach (MODEL($mdlName)->select() as $model) {
+ fputcsv($fp, $model->toArray());
+ }
+ fclose($fp);
+ }
+ }
+
+ protected function export_fixture()
+ {
+ foreach ($this->arguments as $mdlName) {
+ $lines = array();
+
+ $code = array("select();
+ foreach ($models as $model) {
+ $code[] = ' $this->insert(' . $this->createLine($model->toArray()) . ');';
+ }
+
+ $code[] = " }" . PHP_EOL;
+ $code[] = " public function downFixture()";
+ $code[] = " {";
+ $code[] = ' $this->deleteAll();';
+ $code[] = " }";
+ $code[] = "}";
+
+ $path = FIXTURE_DIR . DS . $mdlName . ".php";
+ file_put_contents($path, implode(PHP_EOL, $code));
+
+ $this->success("export $mdlName Records to '" . substr($path, strlen(RUN_BASE) + 1) . "'");
+ }
+ }
+
+ protected function createLine($row)
+ {
+ $line = array();
+ foreach ($row as $col => $val) {
+ if (is_string($val)) {
+ $val = '"' . str_replace('"', '\\"', $val) . '"';
+ } elseif (is_bool($val)) {
+ $val = ($val) ? "true" : "false";
+ } elseif (is_null($val)) {
+ $val = "null";
+ }
+
+ $line[] = "'{$col}' => $val";
+ }
+
+ return "array(" . implode(", ", $line) . ")";
+ }
+}
diff --git a/generator/skeleton/en/tasks/Functional.php b/generator/skeleton/en/tasks/Functional.php
new file mode 100755
index 0000000..b609eed
--- /dev/null
+++ b/generator/skeleton/en/tasks/Functional.php
@@ -0,0 +1,46 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Functional extends Tests
+{
+ public function run()
+ {
+ $runner = Sabel_Test_Runner::create();
+ $runner->setClassPrefix("Functional_");
+
+ $testsDir = RUN_BASE . DS . "tests" . DS . "functional";
+
+ if (count($this->arguments) === 0) {
+ foreach (scandir($testsDir) as $file) {
+ if (preg_match("/^[A-Z].+\.php$/", $file)) {
+ $testName = str_replace(".php", "", $file);
+ $runner->start($testName, $testsDir . DS . $file);
+ }
+ }
+ } else {
+ $testName = $this->arguments[0];
+ $runner->start($testName, $testsDir . DS . $testName . ".php");
+ }
+ }
+
+ public function usage()
+ {
+ echo "Usage: sakle Functional [TESTCASE_NAME]" . PHP_EOL;
+ echo PHP_EOL;
+ echo " TESTCASE_NAME: all run the functional tests, if omit a testcase name" . PHP_EOL;
+ echo PHP_EOL;
+ echo "Example: sakle Functional MyTest" . PHP_EOL;
+ echo PHP_EOL;
+ }
+}
diff --git a/generator/skeleton/en/tasks/Gettext.php b/generator/skeleton/en/tasks/Gettext.php
new file mode 100755
index 0000000..adb847d
--- /dev/null
+++ b/generator/skeleton/en/tasks/Gettext.php
@@ -0,0 +1,174 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Gettext extends Sabel_Sakle_Task
+{
+ private $files = array();
+ private $fileName = "messages.php";
+ private $defaultLocale = "en";
+ private $locales = array();
+
+ public function run()
+ {
+ $dirs = array();
+ $dirs[] = MODULES_DIR_PATH;
+ $dirs[] = RUN_BASE . DS . LIB_DIR_NAME;
+ $dirs[] = RUN_BASE . DS . "public";
+
+ foreach ($dirs as $dir) {
+ $this->addFiles($dir);
+ }
+
+ $this->createOptions();
+ $this->createMessageFiles();
+ }
+
+ private function addFiles($dir)
+ {
+ foreach (scandir($dir) as $item) {
+ if ($item === "." || $item === "..") continue;
+ $item = $dir . DS . $item;
+ if (is_dir($item)) {
+ $this->addFiles($item);
+ } else {
+ $this->files[] = $item;
+ }
+ }
+ }
+
+ private function createMessageFiles()
+ {
+ $dir = RUN_BASE . DS . "locale" . DS;
+ $locales = array();
+
+ foreach (scandir($dir) as $item) {
+ if ($item === "." || $item === "..") continue;
+ if (is_dir($dir . $item)) $locales[] = $item;
+ }
+
+ if (empty($locales)) exit;
+ if (empty($this->files)) exit;
+
+ $messages = array();
+ foreach ($this->files as $file) {
+ $contents = file_get_contents($file);
+ $regex = '/_\(("(.+[^\\\\])"|\'(.+[^\\\\])\')\)/U';
+ preg_match_all($regex, $contents, $matches);
+ if (!empty($matches[1])) {
+ $temp = array();
+
+ if (!empty($matches[2])) {
+ $temp = $matches[2];
+ }
+
+ if (!empty($matches[3])) {
+ foreach ($matches[3] as $k => $v) {
+ $v = str_replace("\\'", "'", $v);
+ if ($v !== "") $temp[$k] = $v;
+ }
+ }
+
+ $messages = array_merge($messages, $temp);
+ }
+ }
+
+ $messages = array_unique($messages);
+ $filePath = DS . $this->fileName;
+
+ foreach ($locales as $locale) {
+ if (!empty($this->locales) && !in_array($locale, $this->locales)) continue;
+ $isDefaultLocale = ($locale === $this->defaultLocale);
+
+ $path = $dir . $locale . $filePath;
+ if (is_file($path)) {
+ $this->marge($path, $messages, $isDefaultLocale);
+ continue;
+ }
+
+ $code = array(" "' . $message . '",' . PHP_EOL;
+ } else {
+ $code[] = '"' . $message . '" => "",' . PHP_EOL;
+ }
+ }
+
+ $code[] = ");";
+ file_put_contents($path, implode("", $code));
+ }
+ }
+
+ private function marge($filePath, $newMessages, $isDefaultLocale)
+ {
+ include ($filePath);
+
+ foreach ($newMessages as $msgid) {
+ if (!isset($messages[$msgid])) {
+ if ($isDefaultLocale) {
+ $messages[$msgid] = $msgid;
+ } else {
+ $messages[$msgid] = "";
+ }
+ }
+ }
+
+ $code = array(" $message) {
+ $code[] = '"' . $msgid . '" => "' . $message . '",' . PHP_EOL;
+ }
+
+ $code[] = ");";
+ file_put_contents($filePath, implode("", $code));
+ }
+
+ private function createOptions()
+ {
+ $args = $this->arguments;
+
+ if (Sabel_Console::hasOption("f", $args)) {
+ $this->fileName = Sabel_Console::getOption("f", $args);
+ }
+
+ /*
+ if (Sabel_Console::hasOption("dl", $args)) {
+ $this->defaultLocale = Sabel_Console::getOption("dl", $args);
+ }
+ */
+
+ if (Sabel_Console::hasOption("l", $args)) {
+ $idx = array_search("-l", $args, true);
+ for ($i = ++$idx, $c = count($args); $i < $c; $i++) {
+ if ($args[$i]{0} === "-") break;
+ $this->locales[] = $args[$i];
+ }
+ }
+
+ $this->arguments = $args;
+ }
+
+ public function usage()
+ {
+ echo "Usage: sakle Gettext [-f FILE_NAME] [-dl DEFAULT_LOCALE] [-l LOCALES]" . PHP_EOL;
+ echo PHP_EOL;
+ echo " -f message filename (default: messages.php)\n";
+ //echo " -dl default locale (default: en)\n";
+ echo " -l locales (default: locales/*)\n";
+ echo PHP_EOL;
+ echo "Example: sakle Gettext -f foo.php -l en fr de" . PHP_EOL;
+ echo PHP_EOL;
+ }
+}
diff --git a/generator/skeleton/en/tasks/Install.php b/generator/skeleton/en/tasks/Install.php
new file mode 100755
index 0000000..2e20edc
--- /dev/null
+++ b/generator/skeleton/en/tasks/Install.php
@@ -0,0 +1,151 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Install extends Sabel_Sakle_Task
+{
+ protected $repo = "http://sabel.php-framework.org";
+
+ public function initialize()
+ {
+ $this->fs = new Sabel_Util_FileSystem(RUN_BASE);
+ }
+
+ public function run()
+ {
+ $args = $this->arguments;
+
+ if (Sabel_Console::hasOption("v", $args)) {
+ $version = Sabel_Console::getOption("v", $args);
+ } else {
+ $version = "HEAD";
+ }
+
+ if (Sabel_Console::hasOption("a", $args)) {
+ $this->installAddon(Sabel_Console::getOption("a", $args), $version);
+ } else {
+ $message = __METHOD__ . "() invalid install option.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ protected function installAddon($addon, $version)
+ {
+ $addon = lcfirst($addon);
+
+ $url = "{$this->repo}/addons/{$addon}/{$version}";
+
+ try {
+ $client = new Sabel_Http_Client($url);
+ $this->_installAddon($addon, $client->request()->getContent());
+ } catch (Exception $e) {
+ $this->warning($e->getMessage());
+ }
+ }
+
+ protected function _installAddon($name, $response)
+ {
+ if (substr($response, 0, 5) === "HEAD:") {
+ return $this->installAddon($name, trim(substr($response, 5)));
+ }
+
+ $addon = Sabel_Xml_Document::create()->loadXML($response);
+
+ if ($addon->tagName !== "addon") {
+ $this->error("addon '{$name}' not found.");
+ exit;
+ }
+
+ $name = $addon->at("name");
+ $version = $addon->at("version");
+
+ $files = $this->getAddonsFiles($addon);
+
+ if (!isset($files["Addon.php"])) {
+ $message = __METHOD__ . "() invalid addon. Addon.php not exists.";
+ }
+
+ $addonClass = ucfirst($name) . "_Addon";
+
+ if (class_exists($addonClass, true)) {
+ $v = constant($addonClass . "::VERSION");
+ if ($v === (float)$version) {
+ $this->message("{$name}_{$version} already installed.");
+ return;
+ } elseif ((float)$version > $v) {
+ $_v = (strpos($v, ".") === false) ? "{$v}.0" : $v;
+ $this->message("upgrade {$name} from {$_v} => {$version}.");
+ } else {
+ $this->message("nothing to install.");
+ return;
+ }
+ }
+
+ $fs = new Sabel_Util_FileSystem(RUN_BASE);
+
+ foreach ($files as $path => $file) {
+ $path = str_replace("/", DS, $path);
+
+ if ($file["backup"] && $fs->isFile($path)) {
+ $oldFile = $path . ".old";
+ $fs->getFile($path)->copyTo(RUN_BASE . DS . $oldFile);
+ $this->message("{$path} saved as {$oldFile}");
+ }
+
+ if (!$fs->isFile($path)) {
+ $fs->mkfile($path)->write($file["source"])->save();
+ } else {
+ $fs->getFile($path)->write($file["source"])->save();
+ }
+
+ $this->success($path);
+ }
+
+ $this->success("install ok: {$name}_{$version}");
+ }
+
+ protected function getAddonsFiles($dom)
+ {
+ $ret = array();
+ $files = $dom->files->item(0);
+ $attrs = $dom->getAttributes()->toArray();
+
+ $baseurl = $this->replaceAttributes($files->at("baseurl"), $attrs);
+
+ foreach ($files->file as $file) {
+ $path = $this->replaceAttributes($file->at("path"), $attrs);
+ $fileUrl = $this->repo . "/" . $baseurl . "/" . $file->at("url");
+ $ret[$path] = array("source" => file_get_contents($fileUrl), "backup" => false);
+ }
+
+ $backup = $dom->backup;
+ if ($backup->length > 0) {
+ foreach ($backup->file as $file) {
+ $path = $this->replaceAttributes($file->at("path"), $attrs);
+ if (isset($ret[$path])) {
+ $ret[$path]["backup"] = true;
+ }
+ }
+ }
+
+ return $ret;
+ }
+
+ protected function replaceAttributes($str, $attrs)
+ {
+ if ($attrs) {
+ foreach ($attrs as $key => $value) {
+ $str = str_replace("{{$key}}", $value, $str);
+ }
+ }
+
+ return $str;
+ }
+}
diff --git a/generator/skeleton/en/tasks/Migration.php b/generator/skeleton/en/tasks/Migration.php
new file mode 100755
index 0000000..28d0d6f
--- /dev/null
+++ b/generator/skeleton/en/tasks/Migration.php
@@ -0,0 +1,319 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Migration extends Sabel_Sakle_Task
+{
+ protected static $startVersion = null;
+ protected static $versions = array();
+ private static $execFinalize = true;
+
+ protected $stmt = null;
+ protected $files = array();
+ protected $migrateTo = 0;
+ protected $metadata = null;
+
+ protected $connectionName = "";
+ protected $currentVersion = 0;
+
+ public function run()
+ {
+ if (count($this->arguments) < 2) {
+ $this->error("to few arguments.");
+ $this->usage();
+ exit;
+ }
+
+ $this->defineEnvironment($this->arguments[0]);
+
+ Sabel_Db_Config::initialize(new Config_Database());
+ $directory = $this->defineMigrationDirectory();
+
+ $connectionName = $this->connectionName = $this->getConnectionName();
+ $this->stmt = Sabel_Db::createStatement($connectionName);
+ $this->metadata = Sabel_Db::createMetadata($connectionName);
+
+ // @todo
+ //if ($this->arguments[1] === "export") {
+ // $this->export();
+ // self::$execFinalize = false;
+ //} else {
+ $tblName = $this->arguments[1];
+ $this->currentVersion = $this->getCurrentVersion();
+ if (in_array($tblName, array("-v", "--version"), true)) {
+ $this->success("CURRENT VERSION: " . $this->currentVersion);
+ exit;
+ } else {
+ $this->execMigration();
+ }
+ //}
+ }
+
+ protected function execMigration()
+ {
+ if (self::$startVersion === null) {
+ self::$startVersion = $this->currentVersion;
+ }
+
+ $this->files = Sabel_Db_Migration_Manager::getFiles();
+
+ if (empty($this->files)) {
+ $this->error("No migration files is found.");
+ exit;
+ }
+
+ if ($this->toVersionNumber($this->arguments[1]) !== false) {
+ if ($this->_execMigration()) {
+ $this->execNextMigration();
+ }
+ }
+ }
+
+ public function finalize()
+ {
+ if (!self::$execFinalize) return;
+
+ $start = self::$startVersion;
+ $end = $this->getCurrentVersion();
+ $mode = ($start < $end) ? "UPGRADE" : "DOWNGRADE";
+
+ $this->success("$mode FROM $start TO $end");
+ }
+
+ protected function getCurrentVersion()
+ {
+ Sabel_Db_Migration_Manager::setStatement($this->stmt);
+ Sabel_Db_Migration_Manager::setMetadata($this->metadata);
+
+ try {
+ if (!in_array("sbl_version", $this->metadata->getTableList())) {
+ $this->createVersioningTable();
+ return 0;
+ } else {
+ return $this->getVersion();
+ }
+ } catch (Exception $e) {
+ $this->error($e->getMessage());
+ exit;
+ }
+ }
+
+ protected function defineMigrationDirectory()
+ {
+ if (Sabel_Console::hasOption("d", $this->arguments)) {
+ $dir = Sabel_Console::getOption("d", $this->arguments);
+ } else {
+ $dir = RUN_BASE . DS . "migration" . DS . $this->getConnectionName();
+ }
+
+ if (!is_dir($dir)) {
+ $this->error("no such directory '{$dir}'.");
+ exit;
+ }
+
+ Sabel_Db_Migration_Manager::setDirectory($dir);
+
+ return $dir;
+ }
+
+ protected function _execMigration()
+ {
+ $version = $this->currentVersion;
+ $to = (int)$this->migrateTo;
+
+ if ((int)$version === $to) {
+ $this->message("NO CHANGES FROM {$to}");
+ exit;
+ }
+
+ $doNext = false;
+ if ($version < $to) {
+ $next = $version + 1;
+ $num = $next;
+ $mode = "upgrade";
+ $doNext = ($next < $to);
+ } else {
+ $next = $version - 1;
+ $num = $version;
+ $mode = "downgrade";
+ $doNext = ($next > $to);
+ }
+
+ Sabel_Db_Migration_Manager::setApplyMode($mode);
+
+ $instance = Sabel_Db::createMigrator($this->connectionName);
+ $directory = Sabel_Db_Migration_Manager::getDirectory();
+ $instance->execute($directory . DS . $this->files[$num]);
+ $this->updateVersionNumber($next);
+
+ return $doNext;
+ }
+
+ protected function execNextMigration()
+ {
+ $instance = new self();
+ $instance->setArguments($this->arguments);
+ $instance->run();
+ }
+
+ protected function toVersionNumber($to)
+ {
+ if (is_numeric($to)) {
+ return $this->migrateTo = $to;
+ }
+
+ switch (strtolower($to)) {
+ case "head":
+ $this->migrateTo = max(array_keys($this->files));
+ break;
+
+ case "foot":
+ $this->migrateTo = 0;
+ break;
+
+ case "rehead":
+ $this->arguments[1] = 0;
+ $this->execNextMigration();
+ $this->success("DOWNGRADE FROM {$this->currentVersion} TO 0");
+ $this->arguments[1] = "head";
+ $this->execNextMigration();
+ $this->success("UPGRADE FROM 0 TO " . $this->getCurrentVersion());
+ $this->arguments[1] = "rehead";
+ return self::$execFinalize = false;
+
+ default:
+ $this->error("version '{$to}' is not supported.");
+ exit;
+ }
+ }
+
+ protected function updateVersionNumber($num)
+ {
+ $stmt = $this->stmt;
+ $table = $stmt->quoteIdentifier("sbl_version");
+ $version = $stmt->quoteIdentifier("version");
+
+ $stmt->setQuery("UPDATE $table SET $version = $num")->execute();
+ }
+
+ protected function getConnectionName()
+ {
+ $args = $this->arguments;
+ return (isset($args[2])) ? $args[2] : "default";
+ }
+
+ protected function createVersioningTable()
+ {
+ $stmt = $this->stmt;
+ $sversion = $stmt->quoteIdentifier("sbl_version");
+ $version = $stmt->quoteIdentifier("version");
+ $createSql = "CREATE TABLE $sversion ({$version} INTEGER NOT NULL)";
+
+ $stmt->setQuery($createSql)->execute();
+ $stmt->setQuery("INSERT INTO {$sversion} VALUES(0)")->execute();
+ }
+
+ protected function getVersion()
+ {
+ $stmt = $this->stmt;
+ $table = $stmt->quoteIdentifier("sbl_version");
+ $version = $stmt->quoteIdentifier("version");
+
+ $rows = $stmt->setQuery("SELECT $version FROM $table")->execute();
+ return (isset($rows[0]["version"])) ? $rows[0]["version"] : 0;
+ }
+
+ protected function export()
+ {
+ $exporter = new MigrationExport($this->metadata, $this->connectionName);
+ $exporter->export();
+ }
+
+ public function usage()
+ {
+ echo "Usage: sakle Migration ENVIRONMENT TO_VERSION [CONNECTION_NAME] " . PHP_EOL;
+ echo PHP_EOL;
+ echo " ENVIRONMENT: production | test | development" . PHP_EOL;
+ echo " TO_VERSION: number of target version | head | foot | rehead" . PHP_EOL;
+ echo " CONNECTION_NAME: " . PHP_EOL;
+ echo PHP_EOL;
+ echo "Example: sakle Migration development head userdb" . PHP_EOL;
+ echo PHP_EOL;
+ }
+}
+
+/*
+class MigrationExport
+{
+ private $fileNum = 1;
+ private $path = "";
+ private $schemas = array();
+ private $exported = array();
+
+ public function __construct($accessor, $connectionName)
+ {
+ $this->schemas = $accessor->getAll();
+ $this->path = RUN_BASE . DS . "migration" . DS . $connectionName;
+ }
+
+ public function export()
+ {
+ if (empty($this->schemas)) return;
+
+ foreach ($this->schemas as $tblName => $schema) {
+ $fkey = $schema->getForeignKey();
+ if ($fkey === null) {
+ $this->doExport($schema);
+ $this->exported[$tblName] = true;
+ unset($this->schemas[$tblName]);
+ continue;
+ }
+
+ $enable = true;
+ foreach ($fkey->toArray() as $key) {
+ $parent = $key->table;
+ if ($parent === $tblName) continue;
+ if (!isset($this->exported[$parent])) {
+ $enable = false;
+ break;
+ }
+ }
+
+ if ($enable) {
+ $this->doExport($schema);
+ $this->exported[$tblName] = true;
+ unset($this->schemas[$tblName]);
+ }
+ }
+
+ $this->export();
+ }
+
+ public function doExport($tblSchema)
+ {
+ $tblName = $tblSchema->getTableName();
+ if ($tblName === "sversion") return;
+
+ $fileName = $this->fileNum . "_" . convert_to_modelname($tblName) . "_create.php";
+ $filePath = $this->path . DS . $fileName;
+
+ Sabel_Console::success("$fileName");
+
+ $writer = new Sabel_Db_Migration_Writer($filePath);
+ $writer->writeTable($tblSchema);
+
+ // @todo
+ $writer->write('$create->options("engine", "InnoDB");');
+ $writer->close();
+
+ $this->fileNum++;
+ }
+}
+*/
diff --git a/generator/skeleton/en/tasks/Schema.php b/generator/skeleton/en/tasks/Schema.php
new file mode 100755
index 0000000..e17500e
--- /dev/null
+++ b/generator/skeleton/en/tasks/Schema.php
@@ -0,0 +1,157 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Schema extends Sabel_Sakle_Task
+{
+ public function run()
+ {
+ clearstatcache();
+ $this->checkInputs();
+
+ $outputDir = RUN_BASE . DS . LIB_DIR_NAME . DS . "schema";
+ $this->defineEnvironment($this->arguments[0]);
+ Sabel_Db_Config::initialize(new Config_Database());
+
+ $isAll = false;
+ $tables = $this->getOutputTables();
+ if (isset($tables[0]) && strtolower($tables[0]) === "all") {
+ $isAll = (count($tables) === 1);
+ }
+
+ $tList = new TableListWriter($outputDir);
+ foreach (Sabel_Db_Config::get() as $connectionName => $params) {
+ Sabel_Db_Config::add($connectionName, $params);
+ $db = Sabel_Db::createMetadata($connectionName);
+ $dbTables = $db->getTableList();
+
+ if ($isAll) {
+ foreach ($dbTables as $tblName) {
+ $writer = new Sabel_Db_Metadata_FileWriter($outputDir);
+ $writer->write($db->getTable($tblName));
+ $this->success("output Schema 'Schema_" . convert_to_modelname($tblName) . "'");
+
+ $tList->add($connectionName, $tblName);
+ }
+ } else {
+ foreach ($tables as $tblName) {
+ if (!in_array($tblName, $dbTables, true)) {
+ $this->error("no such table: {$tblName}");
+ } else {
+ $writer = new Sabel_Db_Metadata_FileWriter($outputDir);
+ $writer->write($db->getTable($tblName));
+ $this->success("output Schema 'Schema_" . convert_to_modelname($tblName) . "'");
+
+ $tList->add($connectionName, $tblName);
+ }
+ }
+ }
+
+ if (Sabel_Console::hasOption("l", $this->arguments)) {
+ $tList->write($connectionName);
+ }
+ }
+ }
+
+ private function getOutputTables()
+ {
+ $args = $this->arguments;
+ if (Sabel_Console::hasOption("t", $args)) {
+ $tables = array();
+ $idx = array_search("-t", $args, true);
+ for ($i = ++$idx, $c = count($args); $i < $c; $i++) {
+ if ($args[$i]{0} === "-") break;
+ $tables[] = $args[$i];
+ }
+
+ return $tables;
+ } else {
+ return array();
+ }
+ }
+
+ private function checkInputs()
+ {
+ $args = $this->arguments;
+
+ if (count($args) < 2) {
+ $this->usage();
+ exit;
+ } elseif ($args[0] === "--help" || $args[0] === "-h") {
+ $this->usage();
+ exit;
+ }
+ }
+
+ public function usage()
+ {
+ echo "Usage: sakle Schema ENVIRONMENT [-l] -t TABLE1 TABLE2..." . PHP_EOL;
+ echo PHP_EOL;
+ echo " ENVIRONMENT: production | test | development" . PHP_EOL;
+ echo PHP_EOL;
+ echo " -l output table list\n";
+ echo " -t output metadata of table\n";
+ echo PHP_EOL;
+ echo "Example: sakle Schema production -l -t foo bar baz" . PHP_EOL;
+ echo PHP_EOL;
+ }
+}
+
+class TableListWriter
+{
+ private $tables = array();
+ private $outputDir = "";
+
+ public function __construct($outputDir)
+ {
+ if (is_dir($outputDir)) {
+ $this->outputDir = $outputDir;
+ } else {
+ $message = "no such file or directory.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public function add($connectionName, $tblName)
+ {
+ $this->tables[$connectionName][] = $tblName;
+ }
+
+ public function get($connectionName)
+ {
+ return $this->tables[$connectionName];
+ }
+
+ public function write($connectionName)
+ {
+ $cn = $connectionName;
+ $fileName = ucfirst($cn) . "TableList";
+ $className = "Schema_" . $fileName;
+
+ Sabel_Console::success("output table list of '{$cn}' database.");
+
+ $contents = array();
+ $contents[] = "tables[$cn]);
+
+ $contents[] = " return array(" . implode(", ", $tables) . ");";
+ $contents[] = " }";
+ $contents[] = "}";
+
+ $fp = fopen($this->outputDir . DS . $fileName . ".php", "w");
+ fwrite($fp, implode(PHP_EOL, $contents));
+ fclose($fp);
+ }
+}
diff --git a/generator/skeleton/en/tasks/Tests.php b/generator/skeleton/en/tasks/Tests.php
new file mode 100755
index 0000000..d301187
--- /dev/null
+++ b/generator/skeleton/en/tasks/Tests.php
@@ -0,0 +1,27 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Tests extends Sabel_Sakle_Task
+{
+ public function initialize()
+ {
+ add_include_path(RUN_BASE . DS . "tests");
+
+ $this->defineEnvironmentByOption("e", TEST);
+
+ if ((ENVIRONMENT & PRODUCTION) > 0) {
+ error_reporting(0);
+ } else {
+ error_reporting(E_ALL|E_STRICT);
+ }
+ }
+}
diff --git a/generator/skeleton/en/tasks/UnitTest.php b/generator/skeleton/en/tasks/UnitTest.php
new file mode 100755
index 0000000..f64ff4a
--- /dev/null
+++ b/generator/skeleton/en/tasks/UnitTest.php
@@ -0,0 +1,48 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class UnitTest extends Tests
+{
+ public function run()
+ {
+ Sabel_Db_Config::initialize(new Config_Database());
+
+ $runner = Sabel_Test_Runner::create();
+ $runner->setClassPrefix("Unit_");
+
+ $testsDir = RUN_BASE . DS . "tests" . DS . "unit";
+
+ if (count($this->arguments) === 0) {
+ foreach (scandir($testsDir) as $file) {
+ if (preg_match("/^[A-Z].+\.php$/", $file)) {
+ $testName = str_replace(".php", "", $file);
+ $runner->start($testName, $testsDir . DS . $file);
+ }
+ }
+ } else {
+ $testName = $this->arguments[0];
+ $runner->start($testName, $testsDir . DS . $testName. ".php");
+ }
+ }
+
+ public function usage()
+ {
+ echo "Usage: sakle UnitTest [TESTCASE_NAME]" . PHP_EOL;
+ echo PHP_EOL;
+ echo " TESTCASE_NAME: all run the unit tests, if omit a testcase name" . PHP_EOL;
+ echo PHP_EOL;
+ echo "Example: sakle UnitTest MyTest" . PHP_EOL;
+ echo PHP_EOL;
+ }
+}
diff --git a/generator/skeleton/en/tests/functional/Index.php b/generator/skeleton/en/tests/functional/Index.php
new file mode 100755
index 0000000..3ddfb13
--- /dev/null
+++ b/generator/skeleton/en/tests/functional/Index.php
@@ -0,0 +1,12 @@
+get("index/index");
+ $response = $this->request($request);
+ $this->assertFalse($this->isRedirected($response));
+ }
+}
diff --git a/generator/skeleton/ja/app/forms/Model.php b/generator/skeleton/ja/app/forms/Model.php
new file mode 100755
index 0000000..e9e54be
--- /dev/null
+++ b/generator/skeleton/ja/app/forms/Model.php
@@ -0,0 +1,165 @@
+modelName)) {
+ $exp = explode("_", get_class($this));
+ $this->modelName = array_pop($exp);
+ }
+
+ if (is_empty($id)) {
+ $this->setModel(MODEL($this->modelName));
+ } else {
+ $this->setModel(MODEL($this->modelName, $id));
+ }
+ }
+
+ public function setModel($model)
+ {
+ if (is_string($model)) {
+ $this->model = MODEL($model);
+ } elseif ($model instanceof Sabel_Db_Model) {
+ $this->model = $model;
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an instance of model.";
+ throw new Sabel_Exception_Runtime();
+ }
+
+ $this->values = $this->model->toArray();
+ }
+
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function validate(Sabel_Validator $validator = null)
+ {
+ if ($validator === null) {
+ $validator = $this->buildValidator();
+ }
+
+ $validator->validate($this->values);
+ $errors = $validator->getErrors();
+
+ if ($uniques = $this->model->getMetadata()->getUniques()) {
+ $this->uniqueCheck($uniques, $errors);
+ }
+
+ $this->errors = $errors;
+
+ return empty($this->errors);
+ }
+
+ public function buildValidator()
+ {
+ $validator = $this->createValidator();
+
+ $this->setupModelValidator($validator);
+ $this->setupValidator($validator);
+
+ return $validator;
+ }
+
+ public function save()
+ {
+ $this->model->setValues($this->values);
+
+ return $this->model->save();
+ }
+
+ protected function uniqueCheck(array $uniques, array &$errors)
+ {
+ $model = $this->model;
+
+ $pkey = $model->getMetadata()->getPrimaryKey();
+ if (!is_array($pkey)) $pkey = array($pkey);
+
+ $inputValues = $this->values;
+ foreach ($uniques as $_uniques) {
+ $fetch = true;
+ $values = array();
+ $finder = new Sabel_Db_Finder($model->getName(), $pkey);
+
+ foreach ($_uniques as $unique) {
+ if (isset($inputValues[$unique])) {
+ $values[] = $inputValues[$unique];
+ $finder->eq($unique, $inputValues[$unique]);
+ } else {
+ $fetch = false;
+ break;
+ }
+ }
+
+ if (!$fetch) continue;
+
+ $_model = $finder->fetch();
+ if ($_model->isSelected()) {
+ $isValid = true;
+ if ($model->isSelected()) { // update
+ foreach ($pkey as $column) {
+ if ($_model->$column !== $model->$column) {
+ $isValid = false;
+ break;
+ }
+ }
+ } else { // insert
+ $isValid = false;
+ }
+
+ if (!$isValid) {
+ $names = array();
+ foreach ($_uniques as $unique) {
+ $names[] = $this->getDisplayName($unique);
+ }
+
+ $errors[] = implode("、", $names) . ' "' . implode(", ", $values) . '" は既に登録されています';
+ }
+ }
+ }
+ }
+
+ protected function setupModelValidator(Sabel_Validator $validator)
+ {
+ $metadata = $this->model->getMetadata();
+ $columns = $metadata->getColumns();
+
+ $validators = $this->validators;
+ foreach ($this->inputNames as $inputName) {
+ if (!isset($columns[$inputName])) continue;
+
+ $column = $columns[$inputName];
+ if ($column->increment) continue;
+
+ if (!$column->nullable) {
+ $validator->add($column->name, "required");
+ }
+
+ if ($column->isString()) {
+ $validator->add($column->name, "strwidth({$column->max})");
+ } elseif ($column->isNumeric()) {
+ $validator->add($column->name, "max({$column->max})");
+ $validator->add($column->name, "min({$column->min})");
+
+ if ($column->isInt()) {
+ $validator->add($column->name, "integer");
+ } else { // float, double
+ $validator->add($column->name, "numeric");
+ }
+ } elseif ($column->isBoolean()) {
+ $validator->add($column->name, "boolean");
+ } elseif ($column->isDate()) {
+ $validator->add($column->name, "date");
+ } elseif ($column->isDatetime()) {
+ $validator->add($column->name, "datetime");
+ }
+ }
+ }
+}
diff --git a/generator/skeleton/ja/app/index/views/index/index.tpl b/generator/skeleton/ja/app/index/views/index/index.tpl
new file mode 100755
index 0000000..2ba8655
--- /dev/null
+++ b/generator/skeleton/ja/app/index/views/index/index.tpl
@@ -0,0 +1,6 @@
+Welcome
+
+
diff --git a/generator/skeleton/ja/app/views/clientError.tpl b/generator/skeleton/ja/app/views/clientError.tpl
new file mode 100755
index 0000000..9a6ae13
--- /dev/null
+++ b/generator/skeleton/ja/app/views/clientError.tpl
@@ -0,0 +1,5 @@
+4xx Client Error
+
+
+ 不正なリクエストにより、処理は中断されました。
+
diff --git a/generator/skeleton/ja/app/views/forbidden.tpl b/generator/skeleton/ja/app/views/forbidden.tpl
new file mode 100755
index 0000000..6e8f26e
--- /dev/null
+++ b/generator/skeleton/ja/app/views/forbidden.tpl
@@ -0,0 +1,5 @@
+403 Forbidden
+
+
+ 要求されたページへのアクセスは制限されています。
+
diff --git a/generator/skeleton/ja/app/views/layout.tpl b/generator/skeleton/ja/app/views/layout.tpl
new file mode 100755
index 0000000..903695a
--- /dev/null
+++ b/generator/skeleton/ja/app/views/layout.tpl
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+ " />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/generator/skeleton/ja/app/views/notFound.tpl b/generator/skeleton/ja/app/views/notFound.tpl
new file mode 100755
index 0000000..8ef7db5
--- /dev/null
+++ b/generator/skeleton/ja/app/views/notFound.tpl
@@ -0,0 +1,6 @@
+404 Not Found
+
+
+ 該当するページが見つかりません。
+ ページは削除されたか、移動された可能性があります。
+
diff --git a/generator/skeleton/ja/app/views/serverError.tpl b/generator/skeleton/ja/app/views/serverError.tpl
new file mode 100755
index 0000000..c0c8bec
--- /dev/null
+++ b/generator/skeleton/ja/app/views/serverError.tpl
@@ -0,0 +1,11 @@
+5xx Server Error
+
+
+ サーバーでエラーが発生し、処理は中断されました。
+
+
+ if (isset($exception_message)) : ?>
+
+
+
+ endif ?>
diff --git a/generator/skeleton/ja/lib/Validator.php b/generator/skeleton/ja/lib/Validator.php
new file mode 100755
index 0000000..e9601c9
--- /dev/null
+++ b/generator/skeleton/ja/lib/Validator.php
@@ -0,0 +1,156 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Validator extends Sabel_Validator
+{
+ public function required($name, $value)
+ {
+ if (is_empty($value)) {
+ return $this->getDisplayName($name) . "を入力してください";
+ }
+ }
+
+ public function integer($name, $value)
+ {
+ if (!is_empty($value) && !is_number($value)) {
+ return $this->getDisplayName($name) . "は整数で入力してください";
+ }
+ }
+
+ public function numeric($name, $value)
+ {
+ if (!is_empty($value) && !is_numeric($value)) {
+ return $this->getDisplayName($name) . "は数値で入力してください";
+ }
+ }
+
+ public function naturalNumber($name, $value)
+ {
+ if (!is_empty($value) && !is_natural_number($value)) {
+ return $this->getDisplayName($name) . "は整数で入力してください";
+ }
+ }
+
+ public function alnum($name, $value)
+ {
+ if (!is_empty($value) && preg_match('/^[0-9a-zA-Z]+$/', $value) === 0) {
+ return $this->getDisplayName($name) . "は半角英数字で入力してください";
+ }
+ }
+
+ public function strlen($name, $value, $max)
+ {
+ if (!is_empty($value) && mb_strlen($value) > $max) {
+ return $this->getDisplayName($name) . "は{$max}文字以内で入力してください";
+ }
+ }
+
+ public function strwidth($name, $value, $max)
+ {
+ if (!is_empty($value) && mb_strwidth($value) > $max) {
+ return $this->getDisplayName($name) . "は全角" . floor($max / 2) . "文字以内で入力してください";
+ }
+ }
+
+ public function max($name, $value, $max)
+ {
+ if (!is_empty($value) && is_number($value) && $value > $max) {
+ return $this->getDisplayName($name) . "は{$max}以下で入力してください";
+ }
+ }
+
+ public function min($name, $value, $min)
+ {
+ if (!is_empty($value) && is_number($value) && $value < $min) {
+ return $this->getDisplayName($name) . "は{$min}以上で入力してください";
+ }
+ }
+
+ public function boolean($name, $value)
+ {
+ if (!is_empty($value) && !in_array($value, array("0", "1", false, true, 0, 1), true)) {
+ return $this->getDisplayName($name) . "の形式が不正です";
+ }
+ }
+
+ public function date($name, $value)
+ {
+ if (!is_empty($value)) {
+ @list ($y, $m, $d) = explode("-", str_replace("/", "-", $value));
+ if (!checkdate($m, $d, $y)) {
+ return $this->getDisplayName($name) . "の形式が不正、または無効な日付です";
+ }
+ }
+ }
+
+ public function datetime($name, $value)
+ {
+ if (!is_empty($value)) {
+ @list ($date, $time) = explode(" ", str_replace("/", "-", $value));
+ @list ($y, $m, $d) = explode("-", $date);
+
+ if (!checkdate($m, $d, $y)) {
+ return $this->getDisplayName($name) . "の形式が不正、または無効な日付です";
+ } else {
+ if (preg_match('/^((0?|1)[\d]|2[0-3]):(0?[\d]|[1-5][\d]):(0?[\d]|[1-5][\d])$/', $time) === 0) {
+ return $this->getDisplayName($name) . "の形式が不正、または無効な日付です";
+ }
+ }
+ }
+ }
+
+ public function image($name, $value, $size = "300K", $validTypes = array())
+ {
+ if (!is_empty($value)) {
+ $data = null;
+ if (is_string($value)) {
+ $data = $value;
+ } elseif (is_object($value) && method_exists($value, "__toString")) {
+ $data = $value->__toString();
+ }
+
+ if (empty($validTypes)) {
+ $validTypes = array("jpeg", "gif", "png");
+ }
+
+ if (!in_array(Sabel_Util_Image::getType($data), $validTypes, true)) {
+ return $this->getDisplayName($name) . "の形式が不正です";
+ } elseif ($size !== null) {
+ if (strlen($data) > strtoint($size)) {
+ return $this->getDisplayName($name) . "のサイズが{$size}Bを超えています";
+ }
+ }
+ }
+ }
+
+ public function same($names, $values)
+ {
+ $ns = array();
+ $comp = true;
+ foreach ($names as $name) {
+ $ns[] = $this->getDisplayName($name);
+ if (is_empty($values[$name])) {
+ $comp = false;
+ }
+ }
+
+ if ($comp) {
+ if (count(array_unique($values)) !== 1) {
+ return implode("と", $ns) . "が一致しません";
+ }
+ }
+ }
+
+ public function nnumber($name, $value)
+ {
+ return $this->naturalNumber($name, $value);
+ }
+}
diff --git a/generator/skeleton/ja/lib/form/Model.php b/generator/skeleton/ja/lib/form/Model.php
new file mode 100755
index 0000000..82e9dc9
--- /dev/null
+++ b/generator/skeleton/ja/lib/form/Model.php
@@ -0,0 +1,169 @@
+modelName = $model->getName();
+ } else {
+ if (is_empty($this->modelName)) {
+ $exp = explode("_", get_class($this));
+ $this->modelName = array_pop($exp);
+ }
+
+ $model = (is_empty($id)) ? MODEL($this->modelName) : MODEL($this->modelName, $id);
+ }
+
+ $this->setModel($model);
+ }
+
+ public function setModel($model)
+ {
+ if (is_string($model)) {
+ $this->model = MODEL($model);
+ } elseif ($model instanceof Sabel_Db_Model) {
+ $this->model = $model;
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an instance of model.";
+ throw new Sabel_Exception_Runtime();
+ }
+
+ $this->values = $this->model->toArray();
+ }
+
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function validate(Sabel_Validator $validator = null)
+ {
+ if ($validator === null) {
+ $validator = $this->buildValidator();
+ }
+
+ $validator->validate($this->values);
+ $errors = $validator->getErrors();
+
+ if ($uniques = $this->model->getMetadata()->getUniques()) {
+ $this->uniqueCheck($uniques, $errors);
+ }
+
+ $this->errors = $errors;
+
+ return empty($this->errors);
+ }
+
+ public function buildValidator()
+ {
+ $validator = $this->createValidator();
+
+ $this->setupModelValidator($validator);
+ $this->setupValidator($validator);
+
+ return $validator;
+ }
+
+ public function save()
+ {
+ $this->model->setValues($this->values);
+
+ return $this->model->save();
+ }
+
+ protected function uniqueCheck(array $uniques, array &$errors)
+ {
+ $model = $this->model;
+
+ $pkey = $model->getMetadata()->getPrimaryKey();
+ if (!is_array($pkey)) $pkey = array($pkey);
+
+ $inputValues = $this->values;
+ foreach ($uniques as $_uniques) {
+ $fetch = true;
+ $values = array();
+ $finder = new Sabel_Db_Finder($model->getName(), $pkey);
+
+ foreach ($_uniques as $unique) {
+ if (isset($inputValues[$unique])) {
+ $values[] = $inputValues[$unique];
+ $finder->eq($unique, $inputValues[$unique]);
+ } else {
+ $fetch = false;
+ break;
+ }
+ }
+
+ if (!$fetch) continue;
+
+ $_model = $finder->fetch();
+ if ($_model->isSelected()) {
+ $isValid = true;
+ if ($model->isSelected()) { // update
+ foreach ($pkey as $column) {
+ if ($_model->$column !== $model->$column) {
+ $isValid = false;
+ break;
+ }
+ }
+ } else { // insert
+ $isValid = false;
+ }
+
+ if (!$isValid) {
+ $names = array();
+ foreach ($_uniques as $unique) {
+ $names[] = $this->getDisplayName($unique);
+ }
+
+ $errors[] = implode("、", $names) . ' "' . implode(", ", $values) . '" は既に登録されています';
+ }
+ }
+ }
+ }
+
+ protected function setupModelValidator(Sabel_Validator $validator)
+ {
+ $metadata = $this->model->getMetadata();
+ $columns = $metadata->getColumns();
+
+ $validators = $this->validators;
+ foreach ($this->inputNames as $inputName) {
+ if (!isset($columns[$inputName])) continue;
+
+ $column = $columns[$inputName];
+ if ($column->increment) continue;
+
+ if (!$column->nullable) {
+ $validator->add($column->name, "required");
+ }
+
+ if ($column->isString()) {
+ $validator->add($column->name, "strwidth({$column->max})");
+ } elseif ($column->isNumeric()) {
+ $validator->add($column->name, "max({$column->max})");
+ $validator->add($column->name, "min({$column->min})");
+
+ if ($column->isInt()) {
+ $validator->add($column->name, "integer");
+ } else { // float, double
+ $validator->add($column->name, "numeric");
+ }
+ } elseif ($column->isBoolean()) {
+ $validator->add($column->name, "boolean");
+ } elseif ($column->isDate()) {
+ $validator->add($column->name, "date");
+ } elseif ($column->isDatetime()) {
+ $validator->add($column->name, "datetime");
+ }
+ }
+ }
+}
diff --git a/sabel/Addon.php b/sabel/Addon.php
new file mode 100755
index 0000000..232739f
--- /dev/null
+++ b/sabel/Addon.php
@@ -0,0 +1,21 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Addon
+{
+ /**
+ * @param Sabel_Bus $bus
+ *
+ * @return void
+ */
+ public function execute(Sabel_Bus $bus);
+}
diff --git a/sabel/Bus.php b/sabel/Bus.php
new file mode 100755
index 0000000..fdb1e56
--- /dev/null
+++ b/sabel/Bus.php
@@ -0,0 +1,254 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Bus extends Sabel_Object
+{
+ /**
+ * @var Sabel_Util_HashList
+ */
+ protected $processorList = null;
+
+ /**
+ * @var Sabel_Config[]
+ */
+ protected $configs = array();
+
+ /**
+ * @var string[]
+ */
+ protected $interfaces = array();
+
+ /**
+ * @var array
+ */
+ protected $holder = array();
+
+ /**
+ * @var object[]
+ */
+ protected $beforeEvent = array();
+
+ /**
+ * @var object[]
+ */
+ protected $afterEvent = array();
+
+ public function __construct()
+ {
+ $this->processorList = new Sabel_Util_HashList();
+ Sabel_Context::getContext()->setBus($this);
+ }
+
+ public static function create(array $data = array())
+ {
+ $bus = new self();
+ if (!empty($data)) $bus->holder = $data;
+
+ return $bus;
+ }
+
+ public function set($key, $value)
+ {
+ if (isset($this->interfaces[$key]) && !$value instanceof $this->interfaces[$key]) {
+ $message = __METHOD__ . "() '{$key}' must be an instance of " . $this->interfaces[$key];
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $this->holder[$key] = $value;
+ }
+
+ public function get($key)
+ {
+ if (array_key_exists($key, $this->holder)) {
+ return $this->holder[$key];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * check bus has a data
+ *
+ * @param mixed string or array
+ * @return bool
+ */
+ public function has($key)
+ {
+ if (is_array($key)) {
+ foreach ($key as $k) {
+ if (!$this->has($k)) return false;
+ }
+
+ return true;
+ } else {
+ return isset($this->holder[$key]);
+ }
+ }
+
+ public function setConfig($name, Sabel_Config $config)
+ {
+ $this->configs[$name] = $config;
+ }
+
+ public function getConfig($name)
+ {
+ if (isset($this->configs[$name])) {
+ return $this->configs[$name];
+ } else {
+ return null;
+ }
+ }
+
+ public function getProcessor($name)
+ {
+ return $this->processorList->get($name);
+ }
+
+ public function getProcessorList()
+ {
+ return $this->processorList;
+ }
+
+ public function run(Sabel_Bus_Config $config)
+ {
+ foreach ($config->getProcessors() as $name => $className) {
+ $this->addProcessor(new $className($name));
+ }
+
+ foreach ($config->getConfigs() as $name => $className) {
+ $this->setConfig($name, new $className());
+ }
+
+ $this->interfaces = $config->getInterfaces();
+ $logging = $config->isLogging();
+
+ try {
+ $logger = Sabel_Logger::create();
+ $processorList = $this->processorList;
+
+ while ($processor = $processorList->next()) {
+ $processorName = $processor->name;
+
+ $beforeEvents = $this->beforeEvent;
+ if (isset($beforeEvents[$processorName])) {
+ foreach ($beforeEvents[$processorName] as $event) {
+ if ($logging) {
+ $logger->write("Bus: beforeEvent " . $event->object->getName() . "::" . $event->method . "()");
+ }
+
+ $event->object->{$event->method}($this);
+ }
+ }
+
+ if ($logging) {
+ $logger->write("Bus: execute " . $processor->name);
+ }
+
+ $processor->execute($this);
+
+ $afterEvents = $this->afterEvent;
+ if (isset($afterEvents[$processorName])) {
+ foreach ($afterEvents[$processorName] as $event) {
+ if ($logging) {
+ $logger->write("Bus: afterEvent " . $event->object->getName() . "::" . $event->method . "()");
+ }
+
+ $event->object->{$event->method}($this);
+ }
+ }
+ }
+
+ $processorList->first();
+ while ($processor = $processorList->next()) {
+ if ($logging) {
+ $logger->write("Bus: shutdown " . $processor->name);
+ }
+
+ $processor->shutdown($this);
+ }
+
+ return $this->get("result");
+ } catch (Exception $e) {
+ $message = get_class($e) . ": " . $e->getMessage();
+ $logger->write($message);
+
+ return ((ENVIRONMENT & DEVELOPMENT) > 0) ? $message : "";
+ }
+ }
+
+ /**
+ * add processor to bus.
+ *
+ * @param Sabel_Bus_Processor $processor
+ * @return Sabel_Bus
+ */
+ public function addProcessor(Sabel_Bus_Processor $processor)
+ {
+ $this->processorList->add($processor->name, $processor);
+ $this->setupEvents($processor);
+
+ return $this;
+ }
+
+ public function insertProcessor($target, Sabel_Bus_Processor $processor, $on)
+ {
+ if ($on !== "before" && $on !== "after") {
+ $message = __METHOD__ . "() argument 3 must be 'before' or 'after'.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $insertMethod = ($on === "before") ? "insertPrevious" : "insertNext";
+ $this->processorList->$insertMethod($target, $processor->name, $processor);
+ $this->setupEvents($processor);
+
+ return $this;
+ }
+
+ public function attachExecuteBeforeEvent($processorName, $object, $method)
+ {
+ $this->attachEvent($processorName, $object, $method, "before");
+ }
+
+ public function attachExecuteAfterEvent($processorName, $object, $method)
+ {
+ $this->attachEvent($processorName, $object, $method, "after");
+ }
+
+ protected function attachEvent($processorName, $object, $method, $when)
+ {
+ $evt = new stdClass();
+ $evt->object = $object;
+ $evt->method = $method;
+
+ $var = $when . "Event";
+ $events =& $this->$var;
+ if (isset($events[$processorName])) {
+ $events[$processorName][] = $evt;
+ } else {
+ $events[$processorName] = array($evt);
+ }
+ }
+
+ protected function setupEvents(Sabel_Bus_Processor $processor)
+ {
+ if ($beforeEvents = $processor->getBeforeEvents()) {
+ foreach ($beforeEvents as $target => $callback) {
+ $this->attachExecuteBeforeEvent($target, $processor, $callback);
+ }
+ }
+
+ if ($afterEvents = $processor->getAfterEvents()) {
+ foreach ($afterEvents as $target => $callback) {
+ $this->attachExecuteAfterEvent($target, $processor, $callback);
+ }
+ }
+ }
+}
diff --git a/sabel/Config.php b/sabel/Config.php
new file mode 100755
index 0000000..f6d89ca
--- /dev/null
+++ b/sabel/Config.php
@@ -0,0 +1,16 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Config
+{
+ public function configure();
+}
diff --git a/sabel/Console.php b/sabel/Console.php
new file mode 100755
index 0000000..aa5ffb4
--- /dev/null
+++ b/sabel/Console.php
@@ -0,0 +1,157 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Console
+{
+ const MSG_INFO = 0x01;
+ const MSG_WARN = 0x02;
+ const MSG_MSG = 0x04;
+ const MSG_ERR = 0x08;
+
+ private $stdin = null;
+ private $ends = array("exit", "quit", "\q");
+
+ private static $headers = array(self::MSG_INFO => "[\x1b[1;32m%s\x1b[m]",
+ self::MSG_WARN => "[\x1b[1;35m%s\x1b[m]",
+ self::MSG_MSG => "[\x1b[1;34m%s\x1b[m]",
+ self::MSG_ERR => "[\x1b[1;31m%s\x1b[m]");
+
+ private static $winHeaders = array(self::MSG_INFO => "[%s]",
+ self::MSG_WARN => "[%s]",
+ self::MSG_MSG => "[%s]",
+ self::MSG_ERR => "[%s]");
+
+ public static function success($msg)
+ {
+ echo self::getHeader(self::MSG_INFO, "SUCCESS") . " $msg" . PHP_EOL;
+ }
+
+ public static function warning($msg)
+ {
+ echo self::getHeader(self::MSG_WARN, "WARNING") . " $msg" . PHP_EOL;
+ }
+
+ public static function message($msg)
+ {
+ echo self::getHeader(self::MSG_MSG, "MESSAGE") . " $msg" . PHP_EOL;
+ }
+
+ public static function error($msg)
+ {
+ echo self::getHeader(self::MSG_ERR, "FAILURE") . " $msg" . PHP_EOL;
+ }
+
+ public static function hasOption($opt, $arguments)
+ {
+ if (strlen($opt) === 1) {
+ foreach ($arguments as $argument) {
+ if (preg_match('/^-[a-zA-Z]*' . $opt . '[a-zA-Z]*$/', $argument)) return true;
+ }
+ } else {
+ foreach ($arguments as $argument) {
+ if (preg_match("/^--{$opt}(=.*)?$/", $argument)) return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function getOption($opt, &$arguments, $unset = true)
+ {
+ $value = null;
+
+ if (strlen($opt) === 1) {
+ $index = array_search("-" . $opt, $arguments, true);
+ if ($index !== false && isset($arguments[$index + 1])) {
+ $value = $arguments[$index + 1];
+ if ($unset) {
+ unset($arguments[$index]);
+ unset($arguments[$index + 1]);
+ $arguments = array_values($arguments);
+ }
+ }
+ } else {
+ foreach ($arguments as $idx => $argument) {
+ if ($argument === "--{$opt}") {
+ if ($unset) {
+ unset($arguments[$idx]);
+ $arguments = array_values($arguments);
+ }
+
+ break;
+ } if (preg_match("/^--{$opt}=(.+)$/", $argument, $matches)) {
+ $value = $matches[1];
+ if ($unset) {
+ unset($arguments[$idx]);
+ $arguments = array_values($arguments);
+ }
+
+ break;
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ public static function getHeader($type, $headMsg)
+ {
+ if ((isset($_SERVER["IS_WINDOWS"]) && $_SERVER["IS_WINDOWS"]) ||
+ DIRECTORY_SEPARATOR === '\\') {
+ return sprintf(self::$winHeaders[$type], $headMsg);
+ } else {
+ return sprintf(self::$headers[$type], $headMsg);
+ }
+ }
+
+ public function __construct($ends = null)
+ {
+ if ($ends !== null) {
+ if (is_array($ends)) {
+ $this->ends = $ends;
+ } else {
+ $message = __METHOD__ . "() argument must be an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ $this->stdin = fopen("php://stdin", "r");
+ }
+
+ public function read($message, $default = null, $trim = true)
+ {
+ if ($default === null) {
+ echo $message . "> ";
+ } else {
+ echo $message . " [{$default}]> ";
+ }
+
+ $input = fgets($this->stdin);
+ $input = ($trim) ? trim($input) : $input;
+
+ if ($input === "" && $default !== null) {
+ $input = $default;
+ }
+
+ if (in_array($input, $this->ends, true)) {
+ return false;
+ } else {
+ return $input;
+ }
+ }
+
+ public function quit()
+ {
+ if (is_resource($this->stdin)) {
+ fclose($this->stdin);
+ }
+ }
+}
diff --git a/sabel/Container.php b/sabel/Container.php
new file mode 100755
index 0000000..e3c367c
--- /dev/null
+++ b/sabel/Container.php
@@ -0,0 +1,651 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Container
+{
+ const SETTER_PREFIX = "set";
+
+ const INJECTION_ANNOTATION = "inject";
+
+ private static $defaultConfig = null;
+
+ /**
+ * @var Sabel_Container_Injection
+ */
+ protected $config = null;
+
+ /**
+ * @var array reflection cache
+ */
+ protected $reflection = null;
+
+ /**
+ * @var array of dependency
+ */
+ protected $dependency = array();
+
+ /**
+ * @var array reflection cache
+ */
+ protected $reflectionCache = array();
+
+ protected $instance = array();
+
+ /**
+ *
+ * @param mixed $config object | string
+ */
+ public static function create($config = null)
+ {
+ if ($config === null) return new self();
+
+ if (!$config instanceof Sabel_Container_Injection) {
+ throw new Sabel_Exception_InvalidArgument("config must be a Sabel_Container_Injection");
+ }
+
+ return new self($config);
+ }
+
+ /**
+ * create new instance with injection config
+ *
+ * @param string $className
+ * @param mixed $config object | string
+ */
+ public static function load()
+ {
+ $args = func_get_args();
+
+ $numArgs = count($args);
+
+ if ($numArgs === 0) {
+ throw new Sabel_Exception_InvalidArgument("must be specify target class");
+ }
+
+ if (is_array($args)) {
+ $class = $args[0];
+ } else {
+ $class = $args;
+ }
+
+ $configs = array();
+
+ if (self::hasDefaultConfig()) {
+ $configs[] = self::getDefaultConfig();
+ }
+
+ if ($numArgs >= 2) {
+ if ($args[1] instanceof Sabel_Container_Injection) {
+ $configs[] = $args[1];
+ }
+
+ for ($i = 2; $i < $numArgs; ++$i) {
+ if ($args[$i] instanceof Sabel_Container_Injection) {
+ $configs[] = $args[$i];
+ }
+ }
+ }
+
+ if (count($configs) >= 2) {
+ $compositeConfig = new Sabel_Container_CompositeConfig();
+
+ foreach ($configs as $config) {
+ $compositeConfig->add($config);
+ }
+
+ $compositeConfig->configure();
+
+ $config = $compositeConfig;
+ } else {
+ if (isset($configs[0])) {
+ $config = $configs[0];
+ } else if (self::hasConfig("default")) {
+ $config = self::getConfig("default");
+ } else {
+ $config = new Sabel_Container_DefaultInjection();
+ }
+ }
+
+ if (!$config instanceof Sabel_Container_Injection) {
+ throw new Sabel_Container_Exception_InvalidConfiguration("configuration not found");
+ }
+
+ return self::create($config)->newInstance($class);
+ }
+
+ /**
+ * set default config
+ *
+ * @param Sabel_Container_Injection $config
+ * @static
+ * @access public
+ * @return void
+ * @throws Sabel_Container_Exception_InvalidConfiguration
+ */
+ public static function setDefaultConfig(Sabel_Container_Injection $config)
+ {
+ if (!$config instanceof Sabel_Container_Injection) {
+ $msg = "object type must be Sabel_Container_Injection";
+ throw new Sabel_Container_Exception_InvalidConfiguration($msg);
+ }
+
+ self::$defaultConfig = $config;
+ }
+
+ /**
+ * has default config
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public static function hasDefaultConfig()
+ {
+ return (self::$defaultConfig !== null);
+ }
+
+ public static function getDefaultConfig()
+ {
+ return self::$defaultConfig;
+ }
+
+ /**
+ * default constructer
+ *
+ * @param Sabel_Container_Injection $injection
+ */
+ public function __construct($config = null)
+ {
+ if ($config !== null) {
+ $config->configure();
+ $this->config = $config;
+ }
+ }
+
+ /**
+ * get new class instance from class name
+ *
+ * @param string $className
+ * @return object
+ */
+ public function newInstance($class, $arguments = null)
+ {
+ $reflection = $this->getReflection($class);
+
+ if (is_object($class)) {
+ $instance = $class;
+ } else {
+ $className = $class;
+
+ if ($reflection->isInstanciatable()) {
+ if (is_array($arguments)) {
+ $instance = $reflection->newInstanceArgs($constructArguments);
+ } elseif (is_string($arguments)) {
+ $instance = $reflection->newInstance($arguments);
+ } else {
+ $instance = $this->newInstanceWithConstruct($reflection, $className);
+ }
+ } else {
+ $binds = $this->config->getBind($className);
+ $bind = (is_array($binds)) ? $binds[0] : $binds;
+
+ $implementation = $bind->getImplementation();
+
+ if ($this->config->hasConstruct($className)) {
+ $instance = $this->newInstanceWithConstructInAbstract($className, $implementation);
+ } elseif (is_array($arguments)) {
+ $instance = $reflection->newInstanceArgs($constructArguments);
+ } elseif (is_string($arguments)) {
+ $instance = $reflection->newInstance($arguments);
+ } else {
+ $instance = $this->newInstance($implementation);
+ }
+ }
+ }
+
+ if ($reflection->hasMethod("setContainerContext")) {
+ $instance->setContainerContext($this);
+ }
+
+ $instance = $this->injectToSetter($reflection, $instance);
+ $instance = $this->applyAspect($instance);
+
+ $this->instance[] = $instance;
+
+ return $instance;
+ }
+
+ /**
+ * inject to setter
+ *
+ * @param Sabel_Reflection_Class $reflection
+ * @param Object $sourceInstance
+ */
+ protected function injectToSetter($reflection, $sourceInstance)
+ {
+ if (self::hasDefaultConfig()) {
+ $defaultConfig = self::getDefaultConfig();
+ $defaultConfig->configure();
+
+ if ($defaultConfig->hasBinds()) {
+ $this->processSetter($reflection, $sourceInstance, $defaultConfig);
+ }
+ }
+
+ if ($this->config->hasBinds()) {
+ $this->processSetter($reflection, $sourceInstance, $this->config);
+ }
+
+ return $sourceInstance;
+ }
+
+ private function processSetter($reflection, $sourceInstance, $config)
+ {
+ foreach ($config->getBinds() as $ifName => $binds) {
+ foreach ($binds as $bind) {
+ if ($bind->hasSetter()) {
+ $injectionMethod = $bind->getSetter();
+ } else {
+ $injectionMethod = self::SETTER_PREFIX . ucfirst($ifName);
+ }
+
+ $implClassName = $bind->getImplementation();
+
+ if (in_array($injectionMethod, get_class_methods($reflection->getName()), true)) {
+ $argumentInstance = $this->newInstanceWithConstruct($reflection, $implClassName);
+
+ if ($reflection->hasMethod($injectionMethod)) {
+ $sourceInstance->$injectionMethod($argumentInstance);
+ }
+ } else {
+ foreach ($reflection->getMethods() as $method) {
+ $injectionMethod = $method->getName();
+
+ $injection = $method->getAnnotation(self::INJECTION_ANNOTATION);
+ $parameters = $method->getParameters();
+
+ if (isset($injection[0][0]) && $injection[0][0] === $ifName) {
+ $argumentInstance = $this->newInstanceWithConstruct($reflection, $implClassName);
+ $sourceInstance->$injectionMethod($argumentInstance);
+ } elseif (isset($parameters[0]) && $injection !== null) {
+ $parameter = $parameters[0];
+
+ $parameterClass = $parameter->getClass();
+
+ if ($parameterClass === null) {
+ throw new Sabel_Container_Exception_InvalidConfiguration("must be type name specified");
+ }
+
+ if ($ifName === $parameterClass->getName()) {
+ $argumentInstance = $this->newInstanceWithConstruct($reflection, $implClassName);
+ $sourceInstance->$injectionMethod($argumentInstance);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected function applyAspect($instance)
+ {
+ if ($instance === null) {
+ throw new Sabel_Exception_Runtime("invalid instance " . var_export($instance, 1));
+ }
+
+ $reflection = $this->getReflection($instance);
+
+ $resultInstance = $this->processAnnotatedAspect($instance, $reflection);
+
+ if ($resultInstance !== null) {
+ return $resultInstance;
+ }
+
+ $className = $reflection->getName();
+ $adviceClasses = array();
+
+ $aspects = $this->config->getAspects();
+
+ if (count($aspects) === 0) {
+ return $instance;
+ }
+
+ $interfaces = $reflection->getInterfaces();
+
+ if (count($interfaces) >= 1) {
+ foreach ($aspects as $aspect) {
+ foreach ($interfaces as $implementInterface) {
+ $implementName = $implementInterface->name;
+
+ $parent = $aspect->getName();
+ if ($implementName instanceof $parent || $aspect->getName() === $implementName) {
+ $adviceClasses[] = $aspect->getAdvice();
+ }
+ }
+ }
+ } else {
+ foreach ($aspects as $aspect) {
+ $parent = $aspect->getName();
+
+ if ($instance instanceof $parent) {
+ $className = $aspect->getName();
+ break;
+ }
+ }
+
+ if (!$this->config->hasAspect($className)) return $instance;
+
+ $adviceClasses[] = $this->config->getAspect($className)->getAdvice();
+ }
+
+ return Sabel_Aspect_Weaver::create()->build($instance, $adviceClasses)
+ ->getProxy();
+ }
+
+ protected function processAnnotatedAspect($instance, $reflection)
+ {
+ $foundAnnotated = false;
+ $aspects = $this->config->getAspects();
+
+ foreach ($aspects as $aspect) {
+ if (!$aspect->hasAnnotated()) {
+ continue;
+ }
+
+ $interfaceName = $aspect->getName();
+
+ if ($instance instanceof $interfaceName) {
+ $annotated = $aspect->getAnnotated();
+ $foundAnnotated = true;
+ break;
+ } elseif (preg_match("/$interfaceName/", $reflection->getName())) {
+ $annotated = $aspect->getAnnotated();
+
+ $foundAnnotated = true;
+ break;
+ }
+ }
+
+ if (!$foundAnnotated) {
+ return null;
+ }
+
+ $weaver = new Sabel_Aspect_Weaver($instance);
+
+ foreach ($reflection->getMethods() as $method) {
+ $methodAnnots = $method->getAnnotations();
+
+ foreach ($methodAnnots as $methodAnnotName => $v) {
+ if (array_key_exists($methodAnnotName, $annotated)) {
+
+ $advisor = new Sabel_Aspect_Advisor_RegexMatcherPointcut();
+ $advisor->setClassMatchPattern("/" . $reflection->getName() . "/");
+ $methodName = $method->getName();
+
+ $interceptor = $annotated[$methodAnnotName][0];
+ $advisor->setMethodMatchPattern("/" . $methodName . "/");
+
+ $advisor->addAdvice(new $interceptor());
+ $weaver->addAdvisor($advisor);
+ }
+ }
+ }
+
+ return $weaver->getProxy();
+ }
+
+ protected function newInstanceWithConstruct($reflection, $className)
+ {
+ if (is_object($className)) {
+ return $className;
+ }
+
+ if (!$this->config->hasConstruct($reflection->getName())) {
+ return $this->newInstanceWithConstructDependency($className);
+ }
+
+ $construct = $this->config->getConstruct($className);
+ $constructArguments = array();
+
+ foreach ($construct->getConstructs() as $constructValue) {
+ if ($this->exists($constructValue)) {
+ $instance = $this->constructInstance($constructValue);
+ $constructArguments[] = $this->applyAspect($instance);
+ } else {
+ $constructArguments[] = $constructValue;
+ }
+ }
+
+ return $reflection->newInstanceArgs($constructArguments);
+ }
+
+ protected function newInstanceWithConstructInAbstract($className, $implClass)
+ {
+ if (is_object($implClass)) {
+ return $implClass;
+ }
+
+ if ($this->config->hasConstruct($className)) {
+ $construct = $this->config->getConstruct($className);
+ $constructArguments = array();
+
+ foreach ($construct->getConstructs() as $constructValue) {
+ if ($this->exists($constructValue)) {
+ // @todo test this condition
+ $instance = $this->constructInstance($constructValue);
+ $constructArguments[] = $this->applyAspect($instance);
+ } else {
+ $constructArguments[] = $constructValue;
+ }
+ }
+
+ $reflect = $this->getReflection($implClass);
+ $instance = $this->applyAspect($reflect->newInstanceArgs($constructArguments));
+
+ return $instance;
+ } else {
+ return $this->applyAspect($this->newInstanceWithConstructDependency($className));
+ }
+ }
+
+ /**
+ * load instance of $className;
+ *
+ * @return object constructed instance
+ */
+ protected function newInstanceWithConstructDependency($className)
+ {
+ $this->scanDependency($className);
+ $instance = $this->buildInstance();
+ unset($this->dependency);
+ $this->dependency = array();
+
+ return $this->applyAspect($instance);
+ }
+
+ protected function constructInstance($className)
+ {
+ $reflection = $this->getReflection($className);
+
+ if (!$reflection->isInterface()) {
+ return $this->newInstance($className);
+ }
+
+ if ($this->config->hasBind($className)) {
+ $bind = $this->config->getBind($className);
+
+ if (is_array($bind)) {
+ $implement = $bind[0]->getImplementation();
+ } else {
+ $implement = $bind->getImplementation();
+ }
+
+ return $this->newInstance($implement);
+ } else {
+ throw new Sabel_Exception_Runtime("any '{$className}' implementation not found");
+ }
+ }
+
+ protected function exists($className)
+ {
+ return (Sabel::using($className) || interface_exists($className));
+ }
+
+ /**
+ * scan dependency
+ *
+ * @todo cycric dependency
+ * @param string $class class name
+ * @throws Sabel_Exception_Runtime when class does not exists
+ */
+ protected function scanDependency($className)
+ {
+ $constructerMethod = "__construct";
+
+ if (!$this->exists($className)) {
+ throw new Sabel_Container_Exception_UndefinedClass("{$className} does't exist");
+ }
+
+ $reflection = $this->getReflection($className);
+
+ $this->dependency[] = $reflection;
+
+ if (!$reflection->hasMethod($constructerMethod)) return $this;
+
+ foreach ($reflection->getMethod($constructerMethod)->getParameters() as $parameter) {
+ if (!$parameter->getClass()) continue;
+
+ $dependClass = $parameter->getClass()->getName();
+
+ if ($this->hasMoreDependency($dependClass)) {
+ $this->scanDependency($dependClass);
+ } else {
+ $this->dependency[] = $this->getReflection($dependClass);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $class class name
+ */
+ protected function hasMoreDependency($class)
+ {
+ $constructerMethod = "__construct";
+
+ $reflection = $this->getReflection($class);
+
+ if ($reflection->isInterface() || $reflection->isAbstract()) return false;
+
+ if ($reflection->hasMethod($constructerMethod)) {
+ $refMethod = new ReflectionMethod($class, $constructerMethod);
+ return (count($refMethod->getParameters()) !== 0);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * construct an all depended classes
+ *
+ * @return object
+ */
+ protected function buildInstance()
+ {
+ $stackCount =(int) count($this->dependency);
+ if ($stackCount < 1) {
+ $msg = "invalid stack count";
+ throw new Sabel_Exception_Runtime($msg);
+ }
+
+ $instance = null;
+
+ for ($i = 0; $i < $stackCount; ++$i) {
+ $reflection = array_pop($this->dependency);
+
+ if ($reflection === null) continue;
+
+ $className = $reflection->getName();
+
+ if ($this->config->hasConstruct($className)) {
+ $instance = $this->newInstance($className);
+ } else {
+ if ($reflection->isInstanciatable()) {
+ $instance = $this->getInstance($className, $instance);
+ } else {
+ $instance = $this->newInstance($className);
+ }
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * get instance of class name
+ */
+ protected function getInstance($className, $instance = null)
+ {
+ if (!$this->exists($className)) {
+ throw new Sabel_Container_Exception_UndefinedClass("class {$clasName} does't exist");
+ }
+
+ if ($instance === null) {
+ return new $className();
+ } else {
+ return new $className($instance);
+ }
+ }
+
+ /**
+ * get reflection class
+ *
+ */
+ protected function getReflection($class)
+ {
+ if (is_object($class)) {
+ $className = get_class($class);
+ } else {
+ $className = $class;
+ }
+
+ if (!isset($this->reflectionCache[$className])) {
+ if (!$this->exists($className)) {
+ throw new Sabel_Container_Exception_UndefinedClass("Class {$className} deos not exist");
+ }
+
+ $reflection = new Sabel_Reflection_Class($class);
+ $this->reflectionCache[$className] = $reflection;
+
+ return $reflection;
+ }
+
+ return $this->reflectionCache[$className];
+ }
+
+ private function getProperties($instance, $reflection)
+ {
+ $values = array();
+ $properties = $reflection->getProperties();
+
+ foreach ($properties as $property) {
+ $pname = $property->getName();
+ $getterMethod = "get" . ucfirst($pname);
+
+ if ($reflection->hasMethod($getterMethod)) {
+ $value = $instance->$getterMethod();
+ $values[$pname] = $value;
+ }
+ }
+
+ return $values;
+ }
+}
diff --git a/sabel/Context.php b/sabel/Context.php
new file mode 100755
index 0000000..771e60f
--- /dev/null
+++ b/sabel/Context.php
@@ -0,0 +1,99 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Context extends Sabel_Object
+{
+ protected static $context = null;
+
+ protected $bus = null;
+ protected $candidate = null;
+ protected $exception = null;
+
+ public static function setContext($context)
+ {
+ self::$context = $context;
+ }
+
+ public static function getContext()
+ {
+ if (self::$context === null) {
+ self::$context = new self();
+ }
+
+ return self::$context;
+ }
+
+ public function setBus($bus)
+ {
+ $this->bus = $bus;
+ }
+
+ public function getBus()
+ {
+ return $this->bus;
+ }
+
+ public function setCandidate($candidate)
+ {
+ $this->candidate = $candidate;
+ }
+
+ public function getCandidate()
+ {
+ return $this->candidate;
+ }
+
+ public function setException($exception)
+ {
+ $this->exception = $exception;
+ }
+
+ public function getException()
+ {
+ return $this->exception;
+ }
+
+ public static function getRequest()
+ {
+ $context = self::getContext();
+ return ($context->bus) ? $context->bus->get("request") : null;
+ }
+
+ public static function getResponse()
+ {
+ $context = self::getContext();
+ return ($context->bus) ? $context->bus->get("response") : null;
+ }
+
+ public static function getDestination()
+ {
+ $context = self::getContext();
+ return ($context->bus) ? $context->bus->get("destination") : null;
+ }
+
+ public static function getSession()
+ {
+ $context = self::getContext();
+ return ($context->bus) ? $context->bus->get("session") : null;
+ }
+
+ public static function getController()
+ {
+ $context = self::getContext();
+ return ($context->bus) ? $context->bus->get("controller") : null;
+ }
+
+ public static function getView()
+ {
+ $context = self::getContext();
+ return ($context->bus) ? $context->bus->get("view") : null;
+ }
+}
diff --git a/sabel/Db.php b/sabel/Db.php
new file mode 100755
index 0000000..99380cb
--- /dev/null
+++ b/sabel/Db.php
@@ -0,0 +1,132 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db
+{
+ /**
+ * @param string $connectionName
+ *
+ * @throws Sabel_Exception_ClassNotFound
+ * @return Sabel_Db_Driver
+ */
+ public static function createDriver($connectionName = "default")
+ {
+ $className = self::classPrefix($connectionName) . "Driver";
+
+ if (Sabel::using($className)) {
+ $driver = new $className($connectionName);
+ } elseif ($baseClass = self::getBaseClassName($connectionName, "Driver")) {
+ $driver = new $baseClass($connectionName);
+ } else {
+ $message = __METHOD__ . "() Class '{$className}' not Found.";
+ throw new Sabel_Exception_ClassNotFound($message);
+ }
+
+ Sabel_Db_Connection::connect($driver);
+
+ return $driver;
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @throws Sabel_Exception_ClassNotFound
+ * @return Sabel_Db_Statement
+ */
+ public static function createStatement($connectionName = "default")
+ {
+ $className = self::classPrefix($connectionName) . "Statement";
+ $driver = self::createDriver($connectionName);
+
+ if (Sabel::using($className)) {
+ $statement = new $className($driver);
+ } elseif ($baseClass = self::getBaseClassName($connectionName, "Statement")) {
+ $statement = new $baseClass($driver);
+ } else {
+ $message = __METHOD__ . "() Class '{$className}' not Found.";
+ throw new Sabel_Exception_ClassNotFound($message);
+ }
+
+ return $statement;
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @throws Sabel_Exception_ClassNotFound
+ * @return Sabel_Db_Abstract_Metadata
+ */
+ public static function createMetadata($connectionName = "default")
+ {
+ $className = self::classPrefix($connectionName) . "Metadata";
+ $schemaName = Sabel_Db_Config::getSchemaName($connectionName);
+
+ if (Sabel::using($className)) {
+ return new $className(self::createDriver($connectionName), $schemaName);
+ } elseif ($baseClass = self::getBaseClassName($connectionName, "Metadata")) {
+ return new $baseClass(self::createDriver($connectionName), $schemaName);
+ } else {
+ $message = __METHOD__ . "() Class '{$className}' not Found.";
+ throw new Sabel_Exception_ClassNotFound($message);
+ }
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @throws Sabel_Exception_ClassNotFound
+ * @return Sabel_Db_Abstract_Migration
+ */
+ public static function createMigrator($connectionName = "default")
+ {
+ $className = self::classPrefix($connectionName) . "Migration";
+
+ if (Sabel::using($className)) {
+ return new $className();
+ } elseif ($baseClass = self::getBaseClassName($connectionName, "Migration")) {
+ return new $baseClass();
+ } else {
+ $message = __METHOD__ . "() Class '{$className}' not Found.";
+ throw new Sabel_Exception_ClassNotFound($message);
+ }
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @return string
+ */
+ private static function classPrefix($connectionName)
+ {
+ $dirs = explode(".", Sabel_Db_Config::getPackage($connectionName));
+ return implode("_", array_map("ucfirst", $dirs)) . "_";
+ }
+
+ /**
+ * @param string $connectionName
+ * @param string $className
+ *
+ * @return mixed
+ */
+ protected static function getBaseClassName($connectionName, $className)
+ {
+ $packageName = Sabel_Db_Config::getPackage($connectionName);
+ $reserved = array("mysql", "pgsql", "oci", "ibase");
+
+ foreach ($reserved as $part) {
+ if (strpos($packageName, $part) !== false) {
+ return "Sabel_Db_" . ucfirst($part) . "_" . $className;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/sabel/Logger.php b/sabel/Logger.php
new file mode 100755
index 0000000..933d232
--- /dev/null
+++ b/sabel/Logger.php
@@ -0,0 +1,97 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Logger extends Sabel_Object
+{
+ /**
+ * @var self
+ */
+ private static $instance = null;
+
+ /**
+ * @var Sabel_Logger_Interface[]
+ */
+ protected $loggers = array();
+
+ /**
+ * @var array
+ */
+ protected $messages = array();
+
+ /**
+ * @var boolean
+ */
+ protected $realtime = false;
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ self::$instance->addLogger(new Sabel_Logger_File());
+ register_shutdown_function(array(self::$instance, "output"));
+ }
+
+ return self::$instance;
+ }
+
+ public function addLogger(Sabel_Logger_Interface $logger)
+ {
+ $this->loggers[] = $logger;
+ }
+
+ public function realtime($bool)
+ {
+ $this->realtime = $bool;
+
+ if ($bool) {
+ $this->output(true);
+ $this->messages = array();
+ }
+ }
+
+ public function write($text, $level = SBL_LOG_INFO, $identifier = "default")
+ {
+ if ((SBL_LOG_LEVEL & $level) === 0) return;
+
+ $message = array("time" => now(), "level" => $level, "message" => $text);
+
+ if ($this->realtime) {
+ $this->_write($identifier, $message);
+ } else {
+ if (array_key_exists($identifier, $this->messages)) {
+ $this->messages[$identifier][] = $message;
+ } else {
+ $this->messages[$identifier] = array($message);
+ }
+ }
+ }
+
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ public function output($force = false)
+ {
+ if ($force || !$this->realtime) {
+ foreach ($this->loggers as $logger) {
+ $logger->output($this->messages);
+ }
+ }
+ }
+
+ protected function _write($identifier, array $message)
+ {
+ foreach ($this->loggers as $logger) {
+ $logger->write($identifier, $message);
+ }
+ }
+}
diff --git a/sabel/Mail.php b/sabel/Mail.php
new file mode 100755
index 0000000..f218ab5
--- /dev/null
+++ b/sabel/Mail.php
@@ -0,0 +1,475 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail extends Sabel_Object
+{
+ const LINELENGTH = 74;
+
+ /**
+ * @var Sabel_Mail_Sender_Interface
+ */
+ protected $sender = null;
+
+ /**
+ * @var Sabel_Mail_Mime_Plain
+ */
+ protected $body = null;
+
+ /**
+ * @var Sabel_Mail_Mime_Html
+ */
+ protected $html = null;
+
+ /**
+ * @var string
+ */
+ protected $boundary = "";
+
+ /**
+ * @var array
+ */
+ protected $headers = array();
+
+ /**
+ * @var array
+ */
+ protected $attachments = array();
+
+ /**
+ * @var boolean
+ */
+ protected $headerEncoding = "base64";
+
+ /**
+ * @var boolean
+ */
+ protected $isMbstringLoaded = false;
+
+ /**
+ * @var string
+ */
+ protected static $EOL = "\r\n";
+
+ public function __construct($charset = "ISO-8859-1", $eol = "\r\n")
+ {
+ self::$EOL = $eol;
+
+ $this->charset = $charset;
+ $this->isMbstringLoaded = extension_loaded("mbstring");
+ }
+
+ public static function setEol($eol)
+ {
+ self::$EOL = $eol;
+ }
+
+ public static function getEol()
+ {
+ return self::$EOL;
+ }
+
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ public function setSender(Sabel_Mail_Sender_Interface $sender)
+ {
+ $this->sender = $sender;
+ }
+
+ public function setHeaderEncoding($encoding)
+ {
+ $this->headerEncoding = strtolower($encoding);
+ }
+
+ public function setFrom($from, $name = "")
+ {
+ if ($name === "") {
+ $this->headers["From"] = array("address" => $from, "name" => "");
+ } else {
+ $this->headers["From"] = array("address" => $from, "name" => $this->encodeHeader($name));
+ }
+
+ return $this;
+ }
+
+ public function setBoundary($boundary)
+ {
+ $this->boundary = $boundary;
+
+ return $this;
+ }
+
+ public function getBoundary()
+ {
+ if ($this->boundary === "") {
+ return $this->boundary = md5hash();
+ } else {
+ return $this->boundary;
+ }
+ }
+
+ public function addTo($to, $name = "")
+ {
+ if ($name === "") {
+ $to = array("address" => $to, "name" => "");
+ } else {
+ $to = array("address" => $to, "name" => $this->encodeHeader($name));
+ }
+
+ if (isset($this->headers["To"])) {
+ $this->headers["To"][] = $to;
+ } else {
+ $this->headers["To"] = array($to);
+ }
+
+ return $this;
+ }
+
+ public function setTo($to)
+ {
+ $this->headers["To"] = array();
+
+ if (is_string($to)) {
+ $this->headers["To"] = array(array("address" => $to, "name" => ""));
+ } elseif (is_array($to)) {
+ foreach ($to as $recipient) {
+ $this->headers["To"][] = array("address" => $recipient, "name" => "");
+ }
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * set a 'Reply-To' header to this mail.
+ *
+ * @param string $replyTo Reply-To address
+ */
+ public function setReplyTo($replyTo)
+ {
+ if (is_string($replyTo)) {
+ $this->headers["Reply-To"] = $replyTo;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ public function addCc($to, $name = "")
+ {
+ if ($name === "") {
+ $to = array("address" => $to, "name" => "");
+ } else {
+ $to = array("address" => $to, "name" => $this->encodeHeader($name));
+ }
+
+ if (isset($this->headers["Cc"])) {
+ $this->headers["Cc"][] = $to;
+ } else {
+ $this->headers["Cc"] = array($to);
+ }
+
+ return $this;
+ }
+
+ public function addBcc($to)
+ {
+ if (isset($this->headers["Bcc"])) {
+ $this->headers["Bcc"][] = $to;
+ } else {
+ $this->headers["Bcc"] = array($to);
+ }
+
+ return $this;
+ }
+
+ public function setSubject($subject)
+ {
+ $this->headers["Subject"] = $this->encodeHeader($subject);
+
+ return $this;
+ }
+
+ public function setBody($text, $encoding = "7bit", $disposition = "inline")
+ {
+ $this->body = new Sabel_Mail_Mime_Plain($text);
+ $this->body->setCharset($this->charset);
+ $this->body->setEncoding($encoding);
+ $this->body->setDisposition($disposition);
+
+ return $this;
+ }
+
+ public function setHtml($text, $encoding = "7bit", $disposition = "inline")
+ {
+ if ($text instanceof Sabel_Mail_Mime_Html) {
+ $this->html = $text;
+ } elseif (is_string($text)) {
+ $this->html = $this->createHtmlPart($text, $encoding, $disposition);
+ } else {
+ $message = __METHOD__ . "() argument must be a string or "
+ . "an instance of Sabel_Mail_Mime_Html";
+
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function createHtmlPart($text, $encoding = "7bit", $disposition = "inline")
+ {
+ $html = new Sabel_Mail_Mime_Html($text);
+ $html->setCharset($this->charset);
+ $html->setEncoding($encoding);
+ $html->setDisposition($disposition);
+
+ return $html;
+ }
+
+ public function attach($name, $data, $mimeType = null, $encoding = "base64", $followRFC2231 = false)
+ {
+ if ($mimeType === null) {
+ $mimeType = get_mime_type($data);
+ if (!$mimeType) $mimeType = "application/octet-stream";
+ }
+
+ if (is_readable($data)) {
+ $data = file_get_contents($data);
+ }
+
+ $file = new Sabel_Mail_Mime_File($name, $data, $mimeType, $followRFC2231);
+ $file->setCharset($this->charset);
+ $file->setEncoding($encoding);
+ $file->setDisposition("attachment");
+
+ $this->attachments[] = $file;
+
+ return $this;
+ }
+
+ public function generateContentId()
+ {
+ $fromAddress = $this->headers["From"]["address"];
+ $this->checkAddressFormat($fromAddress);
+ list (, $host) = explode("@", $fromAddress);
+ return md5hash() . "@" . $host;
+ }
+
+ public function addHeader($name, $value)
+ {
+ $this->headers[$name] = $value;
+
+ return $this;
+ }
+
+ public function getHeader($name)
+ {
+ if (isset($this->headers[$name])) {
+ return $this->headers[$name];
+ } else {
+ return null;
+ }
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function encodeHeader($header)
+ {
+ if ($this->isMbstringLoaded) {
+ $enc = ($this->headerEncoding === "base64") ? "B" : "Q";
+ return mb_encode_mimeheader($header, $this->charset, $enc, self::$EOL);
+ } elseif ($this->headerEncoding === "base64") {
+ return "=?{$this->charset}?B?" . base64_encode($header) . "?=";
+ } else {
+ $quoted = Sabel_Mail_QuotedPrintable::encode($header, self::LINELENGTH, self::$EOL);
+ return "=?{$this->charset}?Q?{$quoted}?=";
+ }
+ }
+
+ public function send(array $options = array())
+ {
+ if ($this->sender === null) {
+ $this->sender = new Sabel_Mail_Sender_PHP();
+ }
+
+ $bodyText = $this->createBodyText();
+ $headers = $this->_setBasicHeaders($this->headers);
+
+ return $this->sender->send($headers, $bodyText, $options);
+ }
+
+ protected function createBodyText()
+ {
+ // empty body.
+ if ($this->body === null && $this->html === null) {
+ $message = __METHOD__ . "() empty body.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ $boundary = $this->getBoundary();
+ $boundary2 = md5hash();
+ $body = array("--{$boundary}");
+
+ list ($hasAttachment, $hasInlineImage) = $this->_setContentType($boundary);
+
+ if ($this->body !== null && $this->html !== null) { // plain & html texts.
+ if ($hasAttachment && $hasInlineImage) {
+ $boundary3 = md5hash();
+ $body[] = 'Content-Type: multipart/alternative; boundary="' . $boundary2 . '"' . self::$EOL;
+ $body[] = "--{$boundary2}";
+ $body[] = $this->body->toMailPart();
+ $body[] = "--{$boundary2}";
+ $body[] = 'Content-Type: multipart/related; boundary="' . $boundary3 . '"' . self::$EOL;
+ $body[] = "--{$boundary3}";
+ $body[] = $this->html->toMailPart($boundary3);
+ $body[] = "--{$boundary3}--" . self::$EOL;
+ $body[] = "--{$boundary2}--" . self::$EOL;
+ $body[] = $this->createAttachmentText($boundary);
+ } elseif ($hasInlineImage) {
+ $body[] = $this->body->toMailPart();
+ $body[] = "--{$boundary}";
+ $body[] = 'Content-Type: multipart/related; boundary="' . $boundary2 . '"' . self::$EOL;
+ $body[] = "--{$boundary2}";
+ $body[] = $this->html->toMailPart($boundary2);
+ $body[] = "--{$boundary2}--" . self::$EOL;
+ } elseif ($hasAttachment) {
+ $body[] = 'Content-Type: multipart/alternative; boundary="' . $boundary2 . '"' . self::$EOL;
+ $body[] = "--{$boundary2}";
+ $body[] = $this->body->toMailPart();
+ $body[] = "--{$boundary2}";
+ $body[] = $this->html->toMailPart();
+ $body[] = "--{$boundary2}--" . self::$EOL;
+ $body[] = $this->createAttachmentText($boundary);
+ } else {
+ $body[] = $this->body->toMailPart();
+ $body[] = "--{$boundary}";
+ $body[] = $this->html->toMailPart();
+ }
+
+ $body[] = "--{$boundary}--";
+ return implode(self::$EOL, $body);
+ } elseif ($this->html !== null) { // only html text.
+ if ($hasAttachment && $hasInlineImage) {
+ $body[] = 'Content-Type: multipart/related; boundary="' . $boundary2 . '"' . self::$EOL;
+ $body[] = "--{$boundary2}";
+ $body[] = $this->html->toMailPart($boundary2);
+ $body[] = "--{$boundary2}--" . self::$EOL;
+ $body[] = $this->createAttachmentText($boundary);
+ } elseif ($hasInlineImage) {
+ $body[] = $this->html->toMailPart($boundary);
+ } elseif ($hasAttachment) {
+ $body[] = $this->html->toMailPart();
+ $body[] = $this->createAttachmentText($boundary);
+ } else {
+ $this->headers["Content-Transfer-Encoding"] = $this->html->getEncoding();
+ return $this->html->toMailPart();
+ }
+
+ $body[] = "--{$boundary}--";
+ return implode(self::$EOL, $body);
+ } else { // only plain text.
+ if ($hasAttachment) {
+ $body = array("--{$boundary}");
+ $body[] = $this->body->toMailPart();
+ $body[] = $this->createAttachmentText($boundary);
+ $body[] = "--{$boundary}--";
+
+ return implode(self::$EOL, $body);
+ } else {
+ $this->headers["Content-Transfer-Encoding"] = $this->body->getEncoding();
+ return $this->body->getEncodedContent();
+ }
+ }
+ }
+
+ protected function _setBasicHeaders(array $headers)
+ {
+ $hasMessageId = false;
+ $hasMimeHeader = false;
+ $hasDate = false;
+
+ foreach ($headers as $name => $header) {
+ $lowered = strtolower($name);
+ if ($lowered === "message-id") {
+ $hasMessageId = true;
+ } elseif ($lowered === "mime-version") {
+ $hasMimeHeader = true;
+ } elseif ($lowered === "date") {
+ $hasDate = true;
+ }
+ }
+
+ if (!$hasMessageId) {
+ $fromAddress = $headers["From"]["address"];
+ $this->checkAddressFormat($fromAddress);
+ list (, $host) = explode("@", $fromAddress);
+ $headers["Message-ID"] = "<" . md5hash() . "@{$host}>";
+ }
+
+ if (!$hasMimeHeader) {
+ $headers["Mime-Version"] = "1.0";
+ }
+
+ if (!$hasDate) {
+ $headers["Date"] = date("r");
+ }
+
+ return $headers;
+ }
+
+ protected function _setContentType($boundary)
+ {
+ $hasAttachment = (count($this->attachments) > 0);
+ $hasInlineImage = (is_object($this->html) && $this->html->hasImage());
+
+ if ($hasAttachment) {
+ $this->headers["Content-Type"] = 'multipart/mixed; boundary="' . $boundary . '"';
+ } elseif ($this->body !== null && $this->html !== null) {
+ $this->headers["Content-Type"] = 'multipart/alternative; boundary="' . $boundary . '"';
+ } elseif ($this->html !== null && $hasInlineImage) {
+ $this->headers["Content-Type"] = 'multipart/related; boundary="' . $boundary . '"';
+ } else {
+ $body = ($this->body !== null) ? $this->body : $this->html;
+ $this->headers["Content-Type"] = $body->getType() . "; charset=" . $this->charset;
+ }
+
+ return array($hasAttachment, $hasInlineImage);
+ }
+
+ protected function createAttachmentText($boundary)
+ {
+ $texts = array();
+ foreach ($this->attachments as $attachment) {
+ $texts[] = "--{$boundary}";
+ $texts[] = $attachment->toMailPart();
+ }
+
+ return implode(self::$EOL, $texts);
+ }
+
+ protected function checkAddressFormat($address)
+ {
+ if (strpos($address, "@") === false) {
+ throw new Sabel_Mail_Exception("atmark not found in address: " . $address);
+ }
+ }
+}
diff --git a/sabel/Object.php b/sabel/Object.php
new file mode 100755
index 0000000..3f9eba1
--- /dev/null
+++ b/sabel/Object.php
@@ -0,0 +1,77 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Object
+{
+ /**
+ * @param string $name
+ *
+ * @return boolean
+ */
+ public final function hasMethod($name)
+ {
+ return method_exists($this, $name);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return get_class($this);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->hashCode();
+ }
+
+ /**
+ * @param object $object
+ *
+ * @return boolean
+ */
+ public function equals($object)
+ {
+ return ($this == $object);
+ }
+
+ /**
+ * @return string
+ */
+ public function hashCode()
+ {
+ return sha1(serialize($this));
+ }
+
+ /**
+ * @return Sabel_Reflection_Class
+ */
+ public function getReflection()
+ {
+ return new Sabel_Reflection_Class($this);
+ }
+
+ /**
+ * an alias for __toString()
+ *
+ * @return string
+ */
+ public final function toString()
+ {
+ return $this->__toString();
+ }
+}
diff --git a/sabel/Preference.php b/sabel/Preference.php
new file mode 100755
index 0000000..d6ec93f
--- /dev/null
+++ b/sabel/Preference.php
@@ -0,0 +1,282 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Preference
+{
+ const TYPE_INT = "int";
+ const TYPE_STRING = "string";
+ const TYPE_FLOAT = "float";
+ const TYPE_BOOLEAN = "boolean";
+ const TYPE_ARRAY = "array";
+ const TYPE_OBJECT = "object";
+
+ private $backend = null;
+
+ public static function create(Sabel_Config $config = null)
+ {
+ if (!$config instanceof Sabel_Config) {
+ return new self();
+ }
+
+ $arrayConfig = $config->configure();
+
+ if (!is_array($arrayConfig)) {
+ $arrayConfig = array();
+ }
+
+ if (isset($arrayConfig["backend"])) {
+ $backendClass = $arrayConfig["backend"];
+
+ if (!class_exists($backendClass)) {
+ $msg = "specified backend class " . $backendClass . " is not found in any classpath";
+ throw new Sabel_Exception_ClassNotFound($msg);
+ }
+
+ $backend = new $backendClass($arrayConfig);
+
+ return new self($backend);
+ }
+ }
+
+ public function __construct($backend = null)
+ {
+ if ($backend == null) {
+ $backend = new Sabel_Preference_Xml();
+ }
+
+ $this->backend = $backend;
+ }
+
+ /**
+ * check backend contains a preference.
+ *
+ * @param $key string
+ */
+ public function contains($key)
+ {
+ return $this->backend->has($key);
+ }
+
+ public function getAll()
+ {
+ $map = array();
+
+ foreach ($this->backend->getAll() as $key => $set) {
+ $map[$key] = $this->convertType($set["value"], $set["type"]);
+ }
+
+ return $map;
+ }
+
+ private function convertType($value, $type)
+ {
+ switch ($type) {
+ case self::TYPE_INT: return (int) $value;
+ case self::TYPE_FLOAT: return (float) $value;
+ case self::TYPE_STRING: return (string) $value;
+ case self::TYPE_BOOLEAN: return (boolean) $value;
+ }
+ }
+
+ public function setInt($key, $value)
+ {
+ if (!is_int($value)) {
+ $value = (int) $value;
+ }
+
+ $this->backend->set($key, $value, self::TYPE_INT);
+ }
+
+ public function getInt($key, $default = null)
+ {
+ if ($default !== null && !is_int($default)) {
+ $default = (int) $default;
+ }
+
+ $result = $this->get($key, $default, self::TYPE_INT);
+
+ if (!is_int($result)) {
+ return (int) $result;
+ }
+
+ return $result;
+ }
+
+ public function setFloat($key, $value)
+ {
+ if (!is_float($value)) {
+ $value = (float) $value;
+ }
+
+ $this->backend->set($key, $value, self::TYPE_FLOAT);
+ }
+
+ public function getFloat($key, $default = null)
+ {
+ if ($default !== null && !is_float($default)) {
+ $default = (float) $default;
+ }
+
+ $result = $this->get($key, $default, self::TYPE_FLOAT);
+
+ if (!is_float($result)) {
+ return (float) $result;
+ }
+
+ return $result;
+ }
+
+ public function setDouble($key, $value)
+ {
+ $this->setFloat($key, $vlaue);
+ }
+
+ public function getDouble($key, $default = null)
+ {
+ $this->getFloat($key, $default);
+ }
+
+ public function setString($key, $value)
+ {
+ if (!is_string($value)) {
+ $value = (string) $value;
+ }
+
+ $this->backend->set($key, $value, self::TYPE_STRING);
+ }
+
+ public function getString($key, $default = null)
+ {
+ if ($default !== null && !is_string($default)) {
+ $default = (string) $default;
+ }
+
+ $result = $this->get($key, $default, self::TYPE_STRING);
+
+ if (!is_string($result)) {
+ return (string) $result;
+ }
+
+ return $result;
+ }
+
+ public function setBoolean($key, $value)
+ {
+ if (!is_bool($value)) {
+ $value = (bool) $value;
+ }
+
+ $this->backend->set($key, $value, self::TYPE_BOOLEAN);
+ }
+
+ public function getBoolean($key, $default = null)
+ {
+ if ($default !== null && !is_bool($default)) {
+ $default = (boolean) $default;
+ }
+
+ $result = $this->get($key, $default, self::TYPE_BOOLEAN);
+
+ if (!is_bool($result)) {
+ return (boolean) $result;
+ }
+
+ return $result;
+ }
+
+ public function setArray($key, $value)
+ {
+ if (!is_array($value)) {
+ throw new Sabel_Exception_Runtime("setArray value must be an array type");
+ }
+
+ $this->backend->set($key, serialize($value), self::TYPE_ARRAY);
+ }
+
+ public function getArray($key, $default = null)
+ {
+ if ($default !== null && !is_array($default)) {
+ throw new Sabel_Exception_Runtime("setArray value must be an array type");
+ }
+
+ if ($default !== null) {
+ $this->setArray($key, $default);
+
+ return $default;
+ }
+
+ if (!$this->backend->has($key) && $default === null) {
+ throw new Sabel_Exception_Runtime("preference ${key} not found");
+ }
+
+ return unserialize($this->backend->get($key));
+ }
+
+ public function setObject($key, $value)
+ {
+ if (!is_object($value)) {
+ throw new Sabel_Exception_Runtime("setObject value must be an object type");
+ }
+
+ $this->backend->set($key, base64_encode(serialize($value)), self::TYPE_OBJECT);
+ }
+
+ public function getObject($key, $default = null)
+ {
+ if ($default !== null && !is_object($default)) {
+ throw new Sabel_Exception_Runtime("setObject value must be an object type");
+ }
+
+ if ($default !== null) {
+ $this->setObject($key, $default);
+
+ return $default;
+ }
+
+ if (!$this->backend->has($key) && $default === null) {
+ throw new Sabel_Exception_Runtime("preference ${key} not found");
+ }
+
+ return unserialize(base64_decode($this->backend->get($key)));
+ }
+
+ /**
+ * delete preference
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function delete($key)
+ {
+ if ($this->backend->has($key)) {
+ $removedValue = $this->backend->get($key);
+
+ $this->backend->delete($key);
+
+ return $removedValue;
+ }
+ }
+
+ private function get($key, $default, $type)
+ {
+ if ($default !== null) {
+ $this->backend->set($key, $default, $type);
+ return $default;
+ }
+
+ if (!$this->backend->has($key) && $default === null) {
+ throw new Sabel_Exception_Runtime("preference ${key} not found");
+ }
+
+ return $this->backend->get($key);
+ }
+}
diff --git a/sabel/Request.php b/sabel/Request.php
new file mode 100755
index 0000000..c13845a
--- /dev/null
+++ b/sabel/Request.php
@@ -0,0 +1,56 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Request
+{
+ const GET = "GET";
+ const POST = "POST";
+ const PUT = "PUT";
+ const DELETE = "DELETE";
+
+ public function setUri($uri);
+ public function getUri();
+
+ public function get($uri);
+ public function post($uri);
+ public function put($uri);
+ public function delete($uri);
+
+ public function isGet();
+ public function isPost();
+ public function isPut();
+ public function isDelete();
+
+ public function setGetValue($name, $value);
+ public function setGetValues(array $values);
+ public function fetchGetValue($name);
+ public function fetchGetValues();
+
+ public function setPostValue($name, $value);
+ public function setPostValues(array $values);
+ public function fetchPostValue($name);
+ public function fetchPostValues();
+
+ public function setParameterValue($name, $value);
+ public function setParameterValues(array $values);
+ public function fetchParameterValue($name);
+ public function fetchParameterValues();
+
+ public function setFile($name, $file);
+ public function setFiles(array $files);
+ public function getFile($name);
+ public function getFiles();
+
+ public function setHttpHeaders(array $headers);
+ public function getHttpHeader($name);
+ public function getHttpHeaders();
+}
diff --git a/sabel/Response.php b/sabel/Response.php
new file mode 100755
index 0000000..8f4906e
--- /dev/null
+++ b/sabel/Response.php
@@ -0,0 +1,89 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Response
+{
+ // 1xx Informational
+ const COUNTINUE = 100;
+ const SWITCHING_PROTOCOLS = 101;
+
+ // 2xx Successful
+ const OK = 200;
+ const CREATED = 201;
+ const ACCEPTED = 202;
+ const NON_AUTHORITATIVE_INFORMATION = 203;
+ const NO_CONTENT = 204;
+ const RESET_CONTENT = 205;
+ const PARTIAL_CONTENT = 206;
+ const MULTI_STATUS = 207;
+
+ // 3xx Redirection
+ const MULTIPLE_CHOICES = 300;
+ const MOVED_PERMANENTLY = 301;
+ const FOUND = 302;
+ const SEE_OTHER = 303;
+ const NOT_MODIFIED = 304;
+ const USE_PROXY = 305;
+
+ // 4xx Client Errors
+ const BAD_REQUEST = 400;
+ const UNAUTHORIZED = 401;
+ const PAYMENT_REQUIRED = 402;
+ const FORBIDDEN = 403;
+ const NOT_FOUND = 404;
+ const METHOD_NOT_ALLOWED = 405;
+ const NOT_ACCEPTABLE = 406;
+ const PROXY_AUTHENTICATION_REQUIRED = 407;
+ const REQUEST_TIMEOUT = 408;
+ const CONFLICT = 409;
+ const GONE = 410;
+ const LENGTH_REQUIRED = 411;
+ const PRECONDITION_FAILED = 412;
+ const REQUEST_ENTITY_TOO_LARGE = 413;
+ const REQUEST_URI_TOO_LONG = 414;
+ const UNSUPPORTED_MEDIA_TYPE = 415;
+ const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ const EXPECTATION_FAILED = 417;
+ const IM_A_TEAPOT = 418;
+
+ // 5xx Server Errors
+ const INTERNAL_SERVER_ERROR = 500;
+ const NOT_IMPLEMENTED = 501;
+ const BAD_GATEWAY = 502;
+ const SERVICE_UNAVAILABLE = 503;
+ const GATEWAY_TIMEOUT = 504;
+ const HTTP_VERSION_NOT_SUPPORTED = 505;
+ const VARIANT_ALSO_NEGOTIATES = 506;
+ const INSUFFICIENT_STORAGE = 507;
+ const NOT_EXTENDED = 510;
+
+ public function getStatus();
+ public function getRedirector();
+
+ public function isSuccess(); // 2xx
+ public function isRedirected(); // 3xx
+ public function isFailure(); // 4xx or 5xx
+
+ public function getLocation();
+ public function setLocation($location);
+
+ public function setResponse($name, $value);
+ public function getResponse($name);
+ public function setResponses(array $responses);
+ public function getResponses();
+
+ public function setHeader($message, $value);
+ public function getHeaders();
+ public function hasHeaders();
+
+ public function outputHeader();
+}
diff --git a/sabel/Sakle.php b/sabel/Sakle.php
new file mode 100755
index 0000000..92f022f
--- /dev/null
+++ b/sabel/Sakle.php
@@ -0,0 +1,64 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sakle
+{
+ public static function run($class)
+ {
+ $args = $_SERVER["argv"];
+ array_shift($args);
+
+ $class = array_shift($args);
+ unshift_include_path(RUN_BASE . DS . "tasks");
+
+ if (class_exists($class, true)) {
+ $ins = new $class();
+ $ins->setArguments($args);
+
+ if (isset($args[0]) && ($args[0] === "-h" || $args[0] === "--help")) {
+ $ins->usage();
+ } else {
+ try {
+ if ($ins->hasMethod("initialize")) {
+ $ins->initialize();
+ }
+
+ $ins->run();
+
+ if ($ins->hasMethod("finalize")) {
+ $ins->finalize();
+ }
+ } catch (Exception $e) {
+ Sabel_Console::error($e->getMessage());
+ }
+ }
+ } else {
+ Sabel_Console::error("such a task doesn't exist.");
+ }
+ }
+}
diff --git a/sabel/Session.php b/sabel/Session.php
new file mode 100755
index 0000000..56905b9
--- /dev/null
+++ b/sabel/Session.php
@@ -0,0 +1,90 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Session
+{
+ /**
+ * @return void
+ */
+ public function start();
+
+ /**
+ * @return boolean
+ */
+ public function isStarted();
+
+ /**
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * @param string
+ *
+ * @return void
+ */
+ public function setId($id);
+
+ /**
+ * @return string
+ */
+ public function getId();
+
+ /**
+ * @return void
+ */
+ public function regenerateId();
+
+ /**
+ * @return boolean
+ */
+ public function has($key);
+
+ /**
+ * @return mixed
+ */
+ public function read($key);
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @param int $timeout
+ *
+ * @return void
+ */
+ public function write($key, $value, $timeout = 0);
+
+ /**
+ * @return mixed
+ */
+ public function delete($key);
+
+ /**
+ * @return array
+ */
+ public function destroy();
+
+ /**
+ * @return string
+ */
+ public function getClientId();
+
+ /**
+ * @return boolean
+ */
+ public function isCookieEnabled();
+
+ /**
+ * @return array
+ */
+ public function getTimeouts();
+}
diff --git a/sabel/Validator.php b/sabel/Validator.php
new file mode 100755
index 0000000..73e849b
--- /dev/null
+++ b/sabel/Validator.php
@@ -0,0 +1,344 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Validator extends Sabel_Object
+{
+ /**
+ * @var array
+ */
+ protected $methods = array();
+
+ /**
+ * @var array
+ */
+ protected $displayNames = array();
+
+ /**
+ * @var array
+ */
+ protected $errors = array();
+
+ /**
+ * @var array
+ */
+ protected $validators = array();
+
+ public function add($name, $method)
+ {
+ if (is_array($method)) {
+ foreach ($method as $m) {
+ $this->add($name, $m);
+ }
+ } else {
+ $v = $this->_parse($method);
+ $m = $v["method"];
+ $a = $v["arguments"];
+
+ if ($m{0} === "-") {
+ $this->delete($name, substr($m, 1));
+ } else {
+ $this->methods[$name][$m] = $a;
+ }
+ }
+
+ return $this;
+ }
+
+ public function delete($name, $method = null)
+ {
+ if ($method === null) {
+ unset($this->methods[$name]);
+ } else {
+ unset($this->methods[$name][$method]);
+ }
+
+ return $this;
+ }
+
+ public function register($validator)
+ {
+ if (is_object($validator)) {
+ $this->validators[] = $validator;
+ } else {
+ $message = __METHOD__ . "() argument must be an object.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ public function __call($method, $arguments)
+ {
+ foreach ($this->validators as $validator) {
+ if (method_exists($validator, $method)) {
+ return call_user_func_array(array($validator, $method), $arguments);
+ }
+ }
+
+ $message = __METHOD__ . "() Call to undefined method.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ public function hasError()
+ {
+ return !empty($this->errors);
+ }
+
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ public function setDisplayNames(array $displayNames)
+ {
+ $this->displayNames = $displayNames;
+ }
+
+ public function setDisplayName($name, $displayName)
+ {
+ $this->displayNames[$name] = $displayName;
+ }
+
+ public function getDisplayName($name)
+ {
+ if (isset($this->displayNames[$name])) {
+ return $this->displayNames[$name];
+ } else {
+ return $name;
+ }
+ }
+
+ public function validate($values)
+ {
+ $errors = array();
+ $methods = $this->methods;
+
+ foreach ($methods as $key => $_methods) {
+ if (strpos($key, ":") !== false) {
+ if (strpos($key, ",") === false) {
+ list ($iname, $dname) = explode(":", $key, 2);
+ $methods[$iname] = $_methods;
+ $this->displayNames[$iname] = $dname;
+ unset($methods[$key]);
+ } else {
+ $names = array();
+ foreach (explode(",", $key) as $k) {
+ list ($iname, $dname) = explode(":", $k, 2);
+ $names[] = $iname;
+ $this->displayNames[$iname] = $dname;
+ }
+
+ $methods[implode(",", $names)] = $_methods;
+ unset($methods[$key]);
+ }
+ }
+ }
+
+ foreach (array_keys($methods) as $key) {
+ if (strpos($key, ",") === false) {
+ if (!isset($values[$key])) {
+ $values[$key] = null;
+ }
+ } else {
+ $vs = array();
+ foreach (explode(",", $key) as $k) {
+ $vs[$k] = (isset($values[$k])) ? $values[$k] : null;
+ }
+
+ $values[$key] = $vs;
+ }
+ }
+
+ foreach ($values as $key => $value) {
+ if (!isset($methods[$key])) continue;
+
+ foreach ($methods[$key] as $method => $arguments) {
+ if (strpos($key, ",") !== false) {
+ $key = explode(",", $key);
+ }
+
+ if (is_empty($arguments)) {
+ if (($message = $this->$method($key, $value)) !== null) {
+ $errors[] = $message;
+ }
+ } else {
+ eval('$message = $this->' . $method . '($key, $value, ' . $arguments . ');');
+ if ($message !== null) $errors[] = $message;
+ }
+ }
+ }
+
+ $this->errors = $errors;
+
+ return empty($errors);
+ }
+
+ public function required($name, $value)
+ {
+ if (is_object($value) && method_exists($value, "__toString")) {
+ $value = $value->__toString();
+ }
+
+ if (is_empty($value)) {
+ return $this->getDisplayName($name) . " is required.";
+ }
+ }
+
+ public function integer($name, $value)
+ {
+ if (!is_empty($value) && !is_number($value)) {
+ return $this->getDisplayName($name) . " must be an integer.";
+ }
+ }
+
+ public function numeric($name, $value)
+ {
+ if (!is_empty($value) && !is_numeric($value)) {
+ return $this->getDisplayName($name) . " must be a numeric.";
+ }
+ }
+
+ public function naturalNumber($name, $value)
+ {
+ if (!is_empty($value) && !is_natural_number($value)) {
+ return $this->getDisplayName($name) . " must be a natural number.";
+ }
+ }
+
+ public function alnum($name, $value)
+ {
+ if (!is_empty($value) && preg_match('/^[0-9a-zA-Z]+$/', $value) === 0) {
+ return $this->getDisplayName($name) . " must be alphanumeric.";
+ }
+ }
+
+ public function strlen($name, $value, $max)
+ {
+ if (!is_empty($value) && strlen($value) > $max) {
+ return $this->getDisplayName($name) . " must be {$max} characters or less.";
+ }
+ }
+
+ public function strwidth($name, $value, $max)
+ {
+ if (!is_empty($value) && strlen($value) > $max) {
+ return $this->getDisplayName($name) . " must be {$max} characters or less.";
+ }
+ }
+
+ public function max($name, $value, $max)
+ {
+ if (!is_empty($value) && is_number($value) && $value > $max) {
+ return $this->getDisplayName($name) . " must be {$max} or less.";
+ }
+ }
+
+ public function min($name, $value, $min)
+ {
+ if (!is_empty($value) && is_number($value) && $value < $min) {
+ return $this->getDisplayName($name) . " must be {$min} or more.";
+ }
+ }
+
+ public function boolean($name, $value)
+ {
+ if (!is_empty($value) && !in_array($value, array("0", "1", false, true, 0, 1), true)) {
+ return "Invalid " . $this->getDisplayName($name) . " foramt.";
+ }
+ }
+
+ public function date($name, $value)
+ {
+ if (!is_empty($value)) {
+ @list ($y, $m, $d) = explode("-", str_replace("/", "-", $value));
+ if (!checkdate($m, $d, $y)) {
+ return "Invalid " . $this->getDisplayName($name) . " format.";
+ }
+ }
+ }
+
+ public function datetime($name, $value)
+ {
+ if (!is_empty($value)) {
+ @list ($date, $time) = explode(" ", str_replace("/", "-", $value));
+ @list ($y, $m, $d) = explode("-", $date);
+
+ if (!checkdate($m, $d, $y)) {
+ return "Invalid " . $this->getDisplayName($name) . " format.";
+ } else {
+ if (preg_match('/^((0?|1)[\d]|2[0-3]):(0?[\d]|[1-5][\d]):(0?[\d]|[1-5][\d])$/', $time) === 0) {
+ return "Invalid " . $this->getDisplayName($name) . " format.";
+ }
+ }
+ }
+ }
+
+ public function image($name, $value, $size = "300K", $validTypes = array())
+ {
+ if (!is_empty($value)) {
+ $data = null;
+
+ if (is_string($value)) {
+ $data = $value;
+ } elseif (is_object($value) && method_exists($value, "__toString")) {
+ $data = $value->__toString();
+ }
+
+ if (empty($validTypes)) {
+ $validTypes = array("jpeg", "gif", "png");
+ }
+
+ if (!in_array(Sabel_Util_Image::getType($data), $validTypes, true)) {
+ return "Invalid " . $this->getDisplayName($name) . " format.";
+ } elseif ($size !== null) {
+ if (strlen($data) > strtoint($size)) {
+ return $this->getDisplayName($name) . " size exceeds {$size}B.";
+ }
+ }
+ }
+ }
+
+ public function same($names, $values)
+ {
+ $ns = array();
+ $comp = true;
+
+ foreach ($names as $name) {
+ $ns[] = $this->getDisplayName($name);
+ if (is_empty($values[$name])) {
+ $comp = false;
+ }
+ }
+
+ if ($comp) {
+ if (count(array_unique($values)) !== 1) {
+ return implode(", ", $ns) . " are not identical.";
+ }
+ }
+ }
+
+ public function nnumber($name, $value)
+ {
+ return $this->naturalNumber($name, $value);
+ }
+
+ private function _parse($validator)
+ {
+ if (is_array($validator)) {
+ return $validator;
+ } elseif (($_pos = strpos($validator, "(")) === false) {
+ return array("method" => $validator, "arguments" => null);
+ } else {
+ return array(
+ "method" => substr($validator, 0, $_pos),
+ "arguments" => substr($validator, $_pos + 1, -1)
+ );
+ }
+ }
+}
diff --git a/sabel/ValueObject.php b/sabel/ValueObject.php
new file mode 100755
index 0000000..a69159f
--- /dev/null
+++ b/sabel/ValueObject.php
@@ -0,0 +1,107 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_ValueObject extends Sabel_Object
+{
+ protected $values = array();
+
+ public static function fromArray(array $values)
+ {
+ $self = new self();
+ $self->addValues($values);
+
+ return $self;
+ }
+
+ public function set($key, $value)
+ {
+ $this->values[$key] = $value;
+
+ return $this;
+ }
+
+ public function __set($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ public function get($key)
+ {
+ if (isset($this->values[$key])) {
+ return $this->values[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ public function __isset($key)
+ {
+ return ($this->get($key) !== null);
+ }
+
+ public function has($key)
+ {
+ return isset($this->values[$key]);
+ }
+
+ public function exists($key)
+ {
+ return array_key_exists($key, $this->values);
+ }
+
+ public function remove($key)
+ {
+ $ret = $this->get($key);
+ unset($this->values[$key]);
+
+ return $ret;
+ }
+
+ public function addValues(array $values)
+ {
+ foreach ($values as $key => $val) {
+ $this->set($key, $val);
+ }
+
+ return $this;
+ }
+
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ public function toArray()
+ {
+ return $this->getValues();
+ }
+
+ public function merge($values)
+ {
+ if (is_array($values)) {
+ $this->addValues($values);
+ } elseif ($values instanceof self) {
+ $this->addValues($values->toArray());
+ } else {
+ $message = __METHOD__ . "() argument must be an array or "
+ . "an instanceof Sabel_ValueObject";
+
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+}
diff --git a/sabel/View.php b/sabel/View.php
new file mode 100755
index 0000000..4ed7e87
--- /dev/null
+++ b/sabel/View.php
@@ -0,0 +1,85 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_View
+{
+ /**
+ * @param string $name
+ * @param Sabel_View_Location $template
+ *
+ * @return void
+ */
+ public function addLocation($name, Sabel_View_Location $template);
+
+ /**
+ * @param string $name
+ *
+ * @return Sabel_View_Location $template
+ */
+ public function getLocation($name);
+
+ /**
+ * @return Sabel_View_Loation[]
+ */
+ public function getLocations();
+
+ /**
+ * @param string $tplName
+ *
+ * @return void
+ */
+ public function setName($tplName);
+
+ /**
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * @param string $tplPath
+ *
+ * @return Sabel_View_Location
+ */
+ public function getValidLocation($tplPath = null);
+
+ /**
+ * @param string $tplPath
+ *
+ * @return string
+ */
+ public function getContents($tplPath = null);
+
+ /**
+ * @param string $name
+ * @param string $tplPath
+ * @param string $contents
+ *
+ * @return void
+ */
+ public function create($name, $tplPath, $contents = "");
+
+ /**
+ * @param string $name
+ * @param string $tplPath
+ *
+ * @return void
+ */
+ public function delete($name, $tplPath);
+
+ /**
+ * @param string $name
+ * @param string $tplPath
+ *
+ * @return boolean
+ */
+ public function isValid($name, $tplPath);
+}
diff --git a/sabel/annotation/Reader.php b/sabel/annotation/Reader.php
new file mode 100755
index 0000000..07fd6cf
--- /dev/null
+++ b/sabel/annotation/Reader.php
@@ -0,0 +1,94 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Annotation_Reader extends Sabel_Object
+{
+ protected static $instance = null;
+
+ private function __construct()
+ {
+
+ }
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function readClassAnnotation($class)
+ {
+ $reflection = new ReflectionClass($class);
+ return $this->process($reflection->getDocComment());
+ }
+
+ public function readMethodAnnotation($class, $method)
+ {
+ $reflection = new ReflectionMethod($class, $method);
+ return $this->process($reflection->getDocComment());
+ }
+
+ public function readPropertyAnnotation($class, $property)
+ {
+ $reflection = new ReflectionProperty($class, $property);
+ return $this->process($reflection->getDocComment());
+ }
+
+ public function process($comment)
+ {
+ $annotations = array();
+ preg_match_all("/@(.+)/", $comment, $comments);
+ if (empty($comments[1])) return $annotations;
+
+ foreach ($comments[1] as $line) {
+ list ($name, $values) = $this->extract(trim($line));
+ $annotations[$name][] = $values;
+ }
+
+ return $annotations;
+ }
+
+ protected function extract($line)
+ {
+ if (($pos = strpos($line, " ")) === false) {
+ return array($line, null);
+ }
+
+ $key = substr($line, 0, $pos);
+ $values = ltrim(substr($line, $pos));
+
+ $regex = '/(".+[^\\\\]")|(\'.+[^\\\\]\')|([^ ]+?)/U';
+ preg_match_all($regex, $values, $matches);
+
+ $annotValues = array();
+ foreach ($matches as $index => $match) {
+ if ($index === 0) continue;
+ foreach ($match as $k => $v) {
+ if ($v === "") continue;
+
+ $quote = $v{0};
+ if (($quote === '"' || $quote === "'") && $quote === $v{strlen($v) - 1}) {
+ $annotValues[$k] = substr(str_replace("\\{$quote}", $quote, $v), 1, -1);;
+ } else {
+ $annotValues[$k] = $v;
+ }
+ }
+ }
+
+ ksort($annotValues);
+
+ return array($key, $annotValues);
+ }
+}
diff --git a/sabel/aspect/Advice.php b/sabel/aspect/Advice.php
new file mode 100755
index 0000000..d4896a0
--- /dev/null
+++ b/sabel/aspect/Advice.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Adviced
+{
+ private $adviced = array();
+
+ public function addAdvice($method, $advice)
+ {
+ $this->adviced[$method][] = $advice;
+ }
+
+ public function addAdvices($method, $advices)
+ {
+ if (isset($this->adviced[$method])) {
+ if (is_array($this->adviced[$method])) {
+ $this->adviced[$method] = array_merge($this->adviced[$method], $advices);
+ } else {
+ $this->adviced[$method] = array_merge(array($this->adviced[$method], $advices));
+ }
+ } else {
+ $this->adviced[$method] = $advices;
+ }
+ }
+
+ public function getAdvice($method)
+ {
+ if (isset($this->adviced[$method])) {
+ return $this->adviced[$method];
+ } else {
+ return array();
+ }
+ }
+
+ public function hasAdvice($method)
+ {
+ return isset($this->adviced[$method]);
+ }
+
+ public function getAllAdvie()
+ {
+ return $this->advied;
+ }
+
+ public function hasAdvices()
+ {
+ return (count($this->adviced) >= 1);
+ }
+
+ public function getAdvices()
+ {
+ return $this->adviced;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/Advices.php b/sabel/aspect/Advices.php
new file mode 100755
index 0000000..42e338a
--- /dev/null
+++ b/sabel/aspect/Advices.php
@@ -0,0 +1,51 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Advices
+{
+ private $advices = array();
+
+ public function addAdvice(Sabel_Aspect_Advice $advice)
+ {
+ if ($advice instanceof Sabel_Aspect_Advice_MethodBefore) {
+ $this->advices[] = new Sabel_Aspect_Interceptor_MethodBeforeAdvice($advice);
+ } elseif ($advice instanceof Sabel_Aspect_Advice_MethodAfterReturning) {
+ $this->advices[] = new Sabel_Aspect_Interceptor_MethodAfterReturningAdvice($advice);
+ } elseif ($advice instanceof Sabel_Aspect_Advice_MethodAfter) {
+ $this->advices[] = new Sabel_Aspect_Interceptor_MethodAfterAdvice($advice);
+ } elseif ($advice instanceof Sabel_Aspect_Advice_MethodThrows) {
+ $this->advices[] = new Sabel_Aspect_Interceptor_MethodThrowsAdvice($advice);
+ } elseif ($advice instanceof Sabel_Aspect_MethodInterceptor) {
+ $this->advices[] = $advice;
+ }
+ }
+
+ public function getAdvices()
+ {
+ return $this->advices;
+ }
+
+ public function toArray()
+ {
+ return $this->advices;
+ }
+
+ public function __toString()
+ {
+ $buffer = array();
+
+ foreach ($this->advices as $advice) {
+ $buffer[] =(string) $advice;
+ }
+
+ return join("\n", $buffer);
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/Advisor.php b/sabel/aspect/Advisor.php
new file mode 100755
index 0000000..f427c5b
--- /dev/null
+++ b/sabel/aspect/Advisor.php
@@ -0,0 +1,17 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Aspect_Advisor
+{
+ public function getAdvice();
+ public function isPerInstance();
+}
\ No newline at end of file
diff --git a/sabel/aspect/DefaultMethodInvocation.php b/sabel/aspect/DefaultMethodInvocation.php
new file mode 100755
index 0000000..4b7d1a7
--- /dev/null
+++ b/sabel/aspect/DefaultMethodInvocation.php
@@ -0,0 +1,146 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_DefaultMethodInvocation implements Sabel_Aspect_MethodInvocation
+{
+ private $proxy = null;
+
+ private $class = null;
+ private $reflection = null;
+
+ private $method = null;
+ private $argument = null;
+
+ private $advices = array();
+
+ private $currentAdviceIndex = -1;
+ private $lastAdviceIndex = -1;
+
+ public function __construct($proxy, $class, $method = null, $argument = null)
+ {
+ $this->proxy = $proxy;
+ $this->class = $class;
+ $this->reflection = new Sabel_Reflection_Class($class);
+
+ if ($method !== null) $this->method = $method;
+ if ($argument !== null) $this->argument = $argument;
+ }
+
+ public function setMethod($method)
+ {
+ $this->method = $method;
+ }
+
+ public function setArgument($argument)
+ {
+ $this->argument = $argument;
+ }
+
+ public function reset($method, $argument)
+ {
+ $this->method = $method;
+ $this->argument = $argument;
+
+ $this->currentAdviceIndex = -1;
+ }
+
+ public function setAdvices($advices)
+ {
+ if (is_array($advices)) {
+ $this->advices = $advices;
+ } else {
+ $this->advices = array($advices);
+ }
+
+ $this->lastAdviceIndex = count($this->advices);
+ }
+
+ /**
+ * implements Sabel_Aspect_Invocation
+ */
+ public function getArguments()
+ {
+ return $this->argument;
+ }
+
+ /**
+ * implements Sabel_Aspect_MethodInvocation
+ */
+ public function getMethod()
+ {
+ try {
+ return $this->reflection->getMethod($this->method);
+ } catch (ReflectionException $e) {
+ return new Sabel_Reflection_DummyMethod($this->method);
+ }
+ }
+
+ /**
+ * implements Sabel_Aspect_Joinpoint
+ */
+ public function getStaticPart()
+ {
+ }
+
+ /**
+ * implements Sabel_Aspect_Joinpoint
+ */
+ public function getThis()
+ {
+ return $this->class;
+ }
+
+ /**
+ * implements Sabel_Aspect_Joinpoint
+ */
+ public function proceed()
+ {
+ if ($this->lastAdviceIndex === -1 || $this->currentAdviceIndex === $this->lastAdviceIndex - 1) {
+
+ try {
+ $method = $this->reflection->getMethod($this->method);
+ return $method->invokeArgs($this->class, $this->argument);
+ } catch (ReflectionException $e) {
+ $method = $this->method;
+ foreach ($this->advices as $advice) {
+ if ($advice instanceof Sabel_Aspect_IntroductionInterceptor) {
+ $advice->$method();
+ }
+ }
+ }
+ }
+
+ if (isset($this->advices[++$this->currentAdviceIndex])) {
+ $advice = $this->advices[$this->currentAdviceIndex];
+
+ if ($advice instanceof Sabel_Aspect_MethodInterceptor) {
+ return $advice->invoke($this);
+ } else {
+ throw new Sabel_Exception_Runtime("advice must be Sabel_Aspect_MethodInterceptor");
+ }
+ }
+ }
+}
+
+class Sabel_Reflection_DummyMethod
+{
+ private $name = "";
+
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/sabel/aspect/DefaultPointcuts.php b/sabel/aspect/DefaultPointcuts.php
new file mode 100755
index 0000000..65a29e5
--- /dev/null
+++ b/sabel/aspect/DefaultPointcuts.php
@@ -0,0 +1,6 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @see org.aopalliance.aop.Invocation
+ */
+interface Sabel_Aspect_Invocation extends Sabel_Aspect_Joinpoint
+{
+ /**
+ * @return array
+ */
+ public function getArguments();
+}
\ No newline at end of file
diff --git a/sabel/aspect/Joinpoint.php b/sabel/aspect/Joinpoint.php
new file mode 100755
index 0000000..89b5765
--- /dev/null
+++ b/sabel/aspect/Joinpoint.php
@@ -0,0 +1,19 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @see org.aopalliance.aop.Joinpoint
+ */
+interface Sabel_Aspect_Joinpoint
+{
+ public function getStaticPart();
+ public function getThis();
+ public function proceed();
+}
\ No newline at end of file
diff --git a/sabel/aspect/MethodInterceptor.php b/sabel/aspect/MethodInterceptor.php
new file mode 100755
index 0000000..343918d
--- /dev/null
+++ b/sabel/aspect/MethodInterceptor.php
@@ -0,0 +1,9 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @see org.aopalliance.aop.MethodInvocation
+ */
+interface Sabel_Aspect_MethodInvocation extends Sabel_Aspect_Invocation
+{
+ /**
+ * @return ReflectionMethod
+ */
+ public function getMethod();
+}
\ No newline at end of file
diff --git a/sabel/aspect/Pointcut.php b/sabel/aspect/Pointcut.php
new file mode 100755
index 0000000..1abd2ed
--- /dev/null
+++ b/sabel/aspect/Pointcut.php
@@ -0,0 +1,17 @@
+getClassMatcher()->matches($class)) {
+ return $pointcut->getMethodMatcher()->matches($method, $class);
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/Proxy.php b/sabel/aspect/Proxy.php
new file mode 100755
index 0000000..f3dcece
--- /dev/null
+++ b/sabel/aspect/Proxy.php
@@ -0,0 +1,85 @@
+target = $targetObject;
+ $this->__setupInvocation();
+
+ if (!$this->invocation instanceof Sabel_Aspect_MethodInvocation) {
+ throw new Sabel_Exception_Runtime("invocation must be setup");
+ }
+ }
+
+ public function __getTarget()
+ {
+ return $this->target;
+ }
+
+ public function __setAdvisor($advisor)
+ {
+ $this->advisor = $advisor;
+ }
+
+ public function __checkTargetMethod($check)
+ {
+ $this->checkTargetMethod = $check;
+ }
+
+ public function __getClassName()
+ {
+ return get_class($this->target);
+ }
+
+ protected function __setupInvocation()
+ {
+ $this->invocation = new Sabel_Aspect_DefaultMethodInvocation($this, $this->target);
+ }
+
+ public function __call($method, $arg)
+ {
+ $reflection = new Sabel_Reflection_Class($this->target);
+
+ if ($this->checkTargetMethod && !$reflection->hasMethod($method)) {
+ throw new Sabel_Aspect_Exception_MethodNotFound($method . " not found");
+ }
+
+ $this->invocation->reset($method, $arg);
+
+ $advices = array();
+
+ $pointcuts = new Sabel_Aspect_DefaultPointcuts();
+
+ foreach ($this->advisor as $advisor) {
+ $pointcut = $advisor->getPointcut();
+
+ if (!$pointcut instanceof Sabel_Aspect_Pointcut)
+ throw new Sabel_Exception_Runtime("pointcut must be Sabel_Aspect_Pointcut");
+
+ if ($pointcuts->matches($pointcut, $method, $this->target)) {
+ $advice = $advisor->getAdvice();
+
+ if (is_array($advice)) {
+ $advices = array_merge($advice, $advices);
+ } else {
+ $advices[] = $advice;
+ }
+ }
+ }
+
+ if (count($advices) >= 1) {
+ $this->invocation->setAdvices($advices);
+ }
+
+ return $this->invocation->proceed();
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/Weaver.php b/sabel/aspect/Weaver.php
new file mode 100755
index 0000000..a4ba226
--- /dev/null
+++ b/sabel/aspect/Weaver.php
@@ -0,0 +1,170 @@
+target = $target;
+ }
+ }
+
+ public static function create($target = null)
+ {
+ return new self($target);
+ }
+
+ public function getAdvice()
+ {
+ return $this->advice;
+ }
+
+ public function build($targetClass, $adviceClasses)
+ {
+ $this->target = $targetClass;
+
+ if (is_array($adviceClasses)) {
+ $this->advices = array();
+ $adviceClasses = array_reverse($adviceClasses);
+
+ foreach ($adviceClasses as $adviceClass) {
+ $this->_build($adviceClass);
+ }
+ } else {
+ $this->_build($adviceClasses);
+ }
+
+ return $this;
+ }
+
+ private function _build($adviceClass)
+ {
+ $this->advice = $advice = new $adviceClass();
+
+ if (isset(self::$reflectionCache[$adviceClass])) {
+ $reflection = self::$reflectionCache[$adviceClass];
+ } else {
+ $reflection = new Sabel_Reflection_Class($advice);
+ self::$reflectionCache[$adviceClass] = $reflection;
+ }
+
+ $annotatedAdvisor = $reflection->getAnnotation("advisor");
+ if ($annotatedAdvisor !== null) {
+ $this->advisorClass = $annotatedAdvisor[0][0];
+ }
+
+ $annotatedInterceptor = $reflection->getAnnotation("interceptor");
+ if ($annotatedInterceptor !== null) {
+ $this->interceptorClass = $annotatedInterceptor[0][0];
+ }
+
+ $annotatedClass = $reflection->getAnnotation("classMatch");
+ if ($annotatedClass !== null) {
+ $this->annotatedClass = $annotatedClass[0][0];
+ }
+
+ foreach ($reflection->getMethods() as $method) {
+ $this->addToAdvisor($method, $advice);
+ }
+ }
+
+ private function addToAdvisor($method, $advice)
+ {
+ $annotation = $method->getAnnotations();
+
+ $type = null;
+ foreach ($this->types as $cType) {
+ if (isset($annotation[$cType])) {
+ $type = $cType;
+ }
+ }
+ if ($type === null) return;
+
+ $pattern = $annotation[$type][0][0];
+ $methodPattern = "/{$pattern}/";
+
+ if (isset($this->methodPatterns[$methodPattern])) {
+ $advisor = $this->methodPatterns[$methodPattern];
+ } else {
+ $advisorClass = $this->advisorClass;
+ $annotatedClass = $this->annotatedClass;
+
+ if (!class_exists($advisorClass, true)) {
+ throw new Sabel_Exception_ClassNotFound($advisorClass);
+ }
+
+ $advisor = new $advisorClass();
+ $advisor->setClassMatchPattern("/{$annotatedClass}/");
+ $advisor->setMethodMatchPattern($methodPattern);
+ $this->methodPatterns[$methodPattern] = $advisor;
+ $this->addAdvisor($advisor);
+ }
+
+ $interceptorClass = $this->interceptorClass;
+ $poInterceptor = new $interceptorClass($advice);
+
+ $setMethod = "set" . ucfirst($type) . "AdviceMethod";
+ $poInterceptor->$setMethod($method->getName());
+
+ $advisor->addAdvice($poInterceptor);
+ }
+
+ public function addAdvisor($advisor, $position = null)
+ {
+ if ($position === null) {
+ $position = count($this->advisor);
+ }
+
+ $this->advisor[$position] = $advisor;
+ }
+
+ /**
+ * @param object $target
+ */
+ public function setTarget($target)
+ {
+ if (class_exists($target)) {
+ $this->target = $target;
+ } else {
+ throw new Sabel_Exception_Runtime("target must be exist class. {$target} not found");
+ }
+ }
+
+ public function getProxy()
+ {
+ if ($this->target === null) {
+ throw new Sabel_Exception_Runtime("must be set target class");
+ }
+
+ if (!is_object($this->target)) {
+ if (class_exists($this->target, true)) {
+ $this->target = new $this->target();
+ }
+ }
+
+ $proxy = new Sabel_Aspect_Proxy($this->target);
+ $proxy->__setAdvisor($this->advisor);
+
+ return $proxy;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/advice/MethodAfter.php b/sabel/aspect/advice/MethodAfter.php
new file mode 100755
index 0000000..e335692
--- /dev/null
+++ b/sabel/aspect/advice/MethodAfter.php
@@ -0,0 +1,6 @@
+pointcut = new Sabel_Aspect_Pointcut_DefaultRegex();
+ $this->advices = new Sabel_Aspect_Advices();
+ }
+
+ public function setClassMatchPattern($pattern)
+ {
+ $this->pointcut->getClassMatcher()->setPattern($pattern);
+ }
+
+ public function setMethodMatchPattern($pattern)
+ {
+ $this->pointcut->getMethodMatcher()->setPattern($pattern);
+ }
+
+ public function addAdvice(Sabel_Aspect_Advice $advice)
+ {
+ $this->advices->addAdvice($advice);
+ }
+
+ /**
+ * implements Sabel_Aspect_Pointcut_Advisor interface
+ */
+ public function getAdvice()
+ {
+ return $this->advices->toArray();
+ }
+
+ /**
+ * implements Sabel_Aspect_Pointcut_Advisor interface
+ */
+ public function getPointcut()
+ {
+ return $this->pointcut;
+ }
+
+ /**
+ * implements Sabel_Aspect_Pointcut_Advisor interface
+ */
+ public function isPerInstance()
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/advisor/StaticMethodMatcher.php b/sabel/aspect/advisor/StaticMethodMatcher.php
new file mode 100755
index 0000000..49ec0f8
--- /dev/null
+++ b/sabel/aspect/advisor/StaticMethodMatcher.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Advisor_StaticMethodMatcher
+ extends Sabel_Aspect_Pointcut_StaticMethodMatcher
+ implements Sabel_Aspect_Pointcut_Advisor
+{
+ private $advice = null;
+
+ public function setAdvice(Sabel_Aspect_Advice $interceptor)
+ {
+ $this->advice = $interceptor;
+ }
+
+ public function getAdvice()
+ {
+ return $this->advice;
+ }
+
+ public function isPerInstance()
+ {
+ }
+
+ public function getPointcut()
+ {
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/advisor/StaticMethodMatcherPointcut.php b/sabel/aspect/advisor/StaticMethodMatcherPointcut.php
new file mode 100755
index 0000000..9ff1cc8
--- /dev/null
+++ b/sabel/aspect/advisor/StaticMethodMatcherPointcut.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Advisor_StaticMethodMatcherPointcut
+ extends Sabel_Aspect_Pointcut_StaticMethodMatcher
+ implements Sabel_Aspect_Pointcut_Advisor
+{
+ private $advice = null;
+
+ public function setAdvice(Sabel_Aspect_Advice $interceptor)
+ {
+ $this->advice = $interceptor;
+ }
+
+ public function getAdvice()
+ {
+ return $this->advice;
+ }
+
+ public function isPerInstance()
+ {
+ }
+
+ public function getPointcut()
+ {
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/exception/MethodNotFound.php b/sabel/aspect/exception/MethodNotFound.php
new file mode 100755
index 0000000..ea0ec24
--- /dev/null
+++ b/sabel/aspect/exception/MethodNotFound.php
@@ -0,0 +1,3 @@
+invokeUnderTrace($invocation, $logger);
+ }
+
+ abstract protected function invokeUnderTrace(Sabel_Aspect_MethodInvocation $invocation, $logger);
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/Debug.php b/sabel/aspect/interceptor/Debug.php
new file mode 100755
index 0000000..842f4f2
--- /dev/null
+++ b/sabel/aspect/interceptor/Debug.php
@@ -0,0 +1,28 @@
+getInvocationDescription($invocation);
+
+ $logger->trace("debug Entering: " . $invocationDescription);
+
+ try {
+ $s = microtime();
+ $rval = $invocation->proceed();
+ $end = (microtime() - $s) * 1000;
+ $logger->trace("debug Exiting: " . $invocationDescription . " with " . var_export($rval, 1));
+ $logger->trace("taking time: " . $end . "ms");
+ return $rval;
+ }catch (Exception $ex) {
+ $logger->trace("Exception thrown in " . $invocationDescription, $ex);
+ throw $ex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/Logger.php b/sabel/aspect/interceptor/Logger.php
new file mode 100755
index 0000000..94d9a84
--- /dev/null
+++ b/sabel/aspect/interceptor/Logger.php
@@ -0,0 +1,9 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Interceptor_MethodAfterAdvice implements Sabel_Aspect_MethodInterceptor
+{
+ private $interceptor = null;
+
+ public function __construct(Sabel_Aspect_Advice_MethodAfterReturning $interceptor)
+ {
+ $this->interceptor = $interceptor;
+ }
+
+ /**
+ * implements Sabel_Aspect_MethodInterceptor
+ */
+ public function invoke(Sabel_Aspect_MethodInvocation $i)
+ {
+ $exception = null;
+
+ try {
+ $return = $i->proceed();
+ $result = $this->interceptor->after($i->getMethod(), $i->getArguments(), $i->getThis(), $return, $exception);
+
+ if ($result !== null) {
+ return $result;
+ } else {
+ return $return;
+ }
+ } catch (Exception $exception) {
+ $result = $this->interceptor->after($i->getMethod(), $i->getArguments(), $i->getThis(), $return, $exception);
+
+ if ($result !== null) {
+ return $result;
+ } else {
+ throw $exception;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/MethodAfterReturningAdvice.php b/sabel/aspect/interceptor/MethodAfterReturningAdvice.php
new file mode 100755
index 0000000..1a33c57
--- /dev/null
+++ b/sabel/aspect/interceptor/MethodAfterReturningAdvice.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Interceptor_MethodAfterReturningAdvice implements Sabel_Aspect_MethodInterceptor
+{
+ private $interceptor = null;
+
+ public function __construct(Sabel_Aspect_Advice_MethodAfterReturning $interceptor)
+ {
+ $this->interceptor = $interceptor;
+ }
+
+ /**
+ * implements Sabel_Aspect_MethodInterceptor
+ */
+ public function invoke(Sabel_Aspect_MethodInvocation $i)
+ {
+ $return = $i->proceed();
+
+ $result = $this->interceptor->after($i->getMethod(), $i->getArguments(), $i->getThis(), $return);
+
+ if ($result !== null) {
+ return $result;
+ } else {
+ return $return;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/MethodBeforeAdvice.php b/sabel/aspect/interceptor/MethodBeforeAdvice.php
new file mode 100755
index 0000000..25d2cd1
--- /dev/null
+++ b/sabel/aspect/interceptor/MethodBeforeAdvice.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Interceptor_MethodBeforeAdvice implements Sabel_Aspect_MethodInterceptor
+{
+ private $interceptor = null;
+
+ public function __construct(Sabel_Aspect_Advice_MethodBefore $interceptor)
+ {
+ $this->interceptor = $interceptor;
+ }
+
+ /**
+ * implements Sabel_Aspect_MethodInterceptor
+ */
+ public function invoke(Sabel_Aspect_MethodInvocation $i)
+ {
+ $result = $this->interceptor->before($i->getMethod(), $i->getArguments(), $i->getThis());
+
+ if ($result !== null) {
+ return $result;
+ } else {
+ return $i->proceed();
+ }
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/MethodThrowsAdvice.php b/sabel/aspect/interceptor/MethodThrowsAdvice.php
new file mode 100755
index 0000000..1c8d709
--- /dev/null
+++ b/sabel/aspect/interceptor/MethodThrowsAdvice.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2008-2011 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Aspect_Interceptor_MethodThrowsAdvice implements Sabel_Aspect_MethodInterceptor
+{
+ private $interceptor = null;
+
+ public function __construct(Sabel_Aspect_Advice_MethodThrows $interceptor)
+ {
+ $this->interceptor = $interceptor;
+ }
+
+ /**
+ * implements Sabel_Aspect_MethodInterceptor
+ */
+ public function invoke(Sabel_Aspect_MethodInvocation $i)
+ {
+ try {
+ return $i->proceed();
+ } catch (Exception $e) {
+ $result = $this->interceptor->throws($i->getMethod(), $i->getArguments(), $i->getThis(), $e);
+
+ if ($result !== null) {
+ return $result;
+ } else {
+ throw $e;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/PlainObjectAdvice.php b/sabel/aspect/interceptor/PlainObjectAdvice.php
new file mode 100755
index 0000000..8485290
--- /dev/null
+++ b/sabel/aspect/interceptor/PlainObjectAdvice.php
@@ -0,0 +1,80 @@
+advice = $advice;
+ }
+
+ public function setBeforeAdviceMethod($method)
+ {
+ $this->adviceMethods["before"] = $method;
+ }
+
+ public function setAfterAdviceMethod($method)
+ {
+ $this->adviceMethods["after"] = $method;
+ }
+
+ public function setAroundAdviceMethod($method)
+ {
+ $this->adviceMethods["around"] = $method;
+ }
+
+ public function setThrowsAdviceMethod($method)
+ {
+ $this->adviceMethods["throws"] = $method;
+ }
+
+ public function invoke(Sabel_Aspect_MethodInvocation $invocation)
+ {
+ $advice = $this->advice;
+ $methods = $this->adviceMethods;
+
+ $method = $invocation->getMethod();
+ $arguments = $invocation->getArguments();
+ $target = $invocation->getThis();
+
+ $hasBefore = isset($methods["before"]);
+ $hasAround = isset($methods["around"]);
+ $hasAfter = isset($methods["after"]);
+ $hasThrows = isset($methods["throws"]);
+
+ try {
+ $result = null;
+
+ if ($hasBefore) {
+ $beforeMethod = $methods["before"];
+ $result = $advice->$beforeMethod($method, $arguments, $target);
+ }
+
+ if ($result === null && !$hasAround) {
+ $result = $invocation->proceed();
+ }
+
+ if ($hasAround) {
+ $aroundMethod = $methods["around"];
+ $result = $advice->$aroundMethod($invocation);
+ }
+ } catch (Exception $exception) {
+ if ($hasThrows) {
+ $throwsMethod = $methods["throws"];
+ $advice->$throwsMethod($method, $arguments, $target, $exception);
+ } else {
+ throw $exception;
+ }
+ }
+
+ if ($hasAfter) {
+ $afterMethod = $methods["after"];
+
+ $advice->$afterMethod($method, $arguments, $target, $result);
+ }
+
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/interceptor/SimpleTrace.php b/sabel/aspect/interceptor/SimpleTrace.php
new file mode 100755
index 0000000..34087f4
--- /dev/null
+++ b/sabel/aspect/interceptor/SimpleTrace.php
@@ -0,0 +1,26 @@
+getInvocationDescription($invocation);
+ $logger->trace("Entering: " . $invocationDescription);
+
+ try {
+ $rval = $invocation->proceed();
+ $logger->trace("Exiting: " . $invocationDescription . " with " . var_export($rval, 1));
+ return $rval;
+ }catch (Exception $ex) {
+ $logger->trace("Exception thrown in " . $invocationDescription, $ex);
+ throw $ex;
+ }
+ }
+
+ protected function getInvocationDescription($invocation)
+ {
+ $fmt = "method '%s' of class[%s]";
+ return sprintf($fmt, $invocation->getMethod()->getName(),
+ $invocation->getThis()->getName());
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/introduction/Advisor.php b/sabel/aspect/introduction/Advisor.php
new file mode 100755
index 0000000..09ae051
--- /dev/null
+++ b/sabel/aspect/introduction/Advisor.php
@@ -0,0 +1,5 @@
+advice = $advice;
+ }
+
+ public function getAdvice()
+ {
+ return $this->advice;
+ }
+
+ public function isPerInstance()
+ {
+
+ }
+
+ public function getPointcut()
+ {
+ return new TrueMatchPointcut();
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/introduction/DelegatingInterceptor.php b/sabel/aspect/introduction/DelegatingInterceptor.php
new file mode 100755
index 0000000..76cddf7
--- /dev/null
+++ b/sabel/aspect/introduction/DelegatingInterceptor.php
@@ -0,0 +1,9 @@
+proceed();
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/introduction/Interceptor.php b/sabel/aspect/introduction/Interceptor.php
new file mode 100755
index 0000000..d04268f
--- /dev/null
+++ b/sabel/aspect/introduction/Interceptor.php
@@ -0,0 +1,5 @@
+pattern = $pattern;
+ }
+
+ public function matches($class)
+ {
+ return (boolean) preg_match($this->pattern, $class);
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/matcher/RegexMethod.php b/sabel/aspect/matcher/RegexMethod.php
new file mode 100755
index 0000000..f7119da
--- /dev/null
+++ b/sabel/aspect/matcher/RegexMethod.php
@@ -0,0 +1,17 @@
+pattern = $pattern;
+ }
+
+ public function matches($method, $class)
+ {
+ return (boolean) preg_match($this->pattern, $method);
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/matcher/StaticClassName.php b/sabel/aspect/matcher/StaticClassName.php
new file mode 100755
index 0000000..831204c
--- /dev/null
+++ b/sabel/aspect/matcher/StaticClassName.php
@@ -0,0 +1,6 @@
+classMatcher = $matcher;
+ }
+
+ public function setMethodMatcher(Sabel_Aspect_MethodMatcher $matcher)
+ {
+ $this->methodMatcher = $matcher;
+ }
+}
\ No newline at end of file
diff --git a/sabel/aspect/pointcut/Advisor.php b/sabel/aspect/pointcut/Advisor.php
new file mode 100755
index 0000000..856eab5
--- /dev/null
+++ b/sabel/aspect/pointcut/Advisor.php
@@ -0,0 +1,6 @@
+classMatcher = new Sabel_Aspect_Matcher_RegexClass();
+ $this->methodMatcher = new Sabel_Aspect_Matcher_RegexMethod();
+ }
+
+ public function setClassMatchPattern($pattern)
+ {
+ $this->classMatcher->setPattern($pattern);
+ }
+
+ public function setMethodMatchPattern($pattern)
+ {
+ $this->methodMatcher->setPattern($pattern);
+ }
+
+ public function getClassMatcher()
+ {
+ return $this->classMatcher;
+ }
+
+ public function getMethodMatcher()
+ {
+ return $this->methodMatcher;
+ }
+}
diff --git a/sabel/aspect/pointcut/Regex.php b/sabel/aspect/pointcut/Regex.php
new file mode 100755
index 0000000..a8c671b
--- /dev/null
+++ b/sabel/aspect/pointcut/Regex.php
@@ -0,0 +1,7 @@
+classMatcher = $matcher;
+ }
+
+ /**
+ * implements from Pointcut interface
+ */
+ public function getClassMatcher()
+ {
+ return $this->classMatcher;
+ }
+
+ /**
+ * implements from Pointcut interface
+ */
+ public function getMethodMatcher()
+ {
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/sabel/bus/Config.php b/sabel/bus/Config.php
new file mode 100755
index 0000000..26f5ae5
--- /dev/null
+++ b/sabel/bus/Config.php
@@ -0,0 +1,66 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Bus_Config extends Sabel_Object
+{
+ /**
+ * @var Sabel_Bus_Processor[]
+ */
+ protected $processors = array();
+
+ /**
+ * @var Sabel_Config[]
+ */
+ protected $configs = array();
+
+ /**
+ * @var string[]
+ */
+ protected $interfaces = array();
+
+ /**
+ * @var boolean
+ */
+ protected $logging = false;
+
+ /**
+ * @return Sabel_Bus_Processor[]
+ */
+ public function getProcessors()
+ {
+ return $this->processors;
+ }
+
+ /**
+ * @return Sabel_Config[]
+ */
+ public function getConfigs()
+ {
+ return $this->configs;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getInterfaces()
+ {
+ return $this->interfaces;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isLogging()
+ {
+ return $this->logging;
+ }
+}
diff --git a/sabel/bus/Processor.php b/sabel/bus/Processor.php
new file mode 100755
index 0000000..2b254e1
--- /dev/null
+++ b/sabel/bus/Processor.php
@@ -0,0 +1,96 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Bus_Processor extends Sabel_Object
+{
+ /**
+ * @var string
+ */
+ public $name = "";
+
+ /**
+ * @var array
+ */
+ protected $properties = array();
+
+ /**
+ * @var array
+ */
+ protected $beforeEvents = array();
+
+ /**
+ * @var array
+ */
+ protected $afterEvents = array();
+
+ abstract public function execute(Sabel_Bus $bus);
+
+ public function __construct($name)
+ {
+ if ($name === null || $name === "") {
+ $message = __METHOD__ . "() name must be set.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $this->name = $name;
+ }
+
+ public function __set($key, $val)
+ {
+ $this->properties[$key] = $val;
+ }
+
+ public function __get($key)
+ {
+ if (isset($this->properties[$key])) {
+ return $this->properties[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public final function getBeforeEvents()
+ {
+ return $this->beforeEvents;
+ }
+
+ public final function getAfterEvents()
+ {
+ return $this->afterEvents;
+ }
+
+ public function extract(/* string[] args */)
+ {
+ $names = func_get_args();
+
+ if (count($names) > 0) {
+ $bus = $this->getBus();
+ if (is_array($names[0])) {
+ $names = $names[0];
+ }
+
+ foreach ($names as $name) {
+ $this->properties[$name] = $bus->get($name);
+ }
+ }
+ }
+
+ /**
+ * @param Sabel_Bus $bus
+ *
+ * @return void
+ */
+ public function shutdown(Sabel_Bus $bus)
+ {
+
+ }
+}
diff --git a/sabel/cache/Apc.php b/sabel/cache/Apc.php
new file mode 100755
index 0000000..be7fdb4
--- /dev/null
+++ b/sabel/cache/Apc.php
@@ -0,0 +1,54 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cache_Apc implements Sabel_Cache_Interface
+{
+ private static $instance = null;
+
+ /**
+ * @var Sabel_Kvs_Apc
+ */
+ protected $kvs = null;
+
+ private function __construct()
+ {
+ if (extension_loaded("apc")) {
+ $this->kvs = Sabel_Kvs_Apc::create();
+ } else {
+ $message = __METHOD__ . "() apc extension not loaded.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function read($key)
+ {
+ return $this->kvs->read($key);
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+ $this->kvs->write($key, $value, $timeout);
+ }
+
+ public function delete($key)
+ {
+ return $this->kvs->delete($key);
+ }
+}
diff --git a/sabel/cache/File.php b/sabel/cache/File.php
new file mode 100755
index 0000000..c61ae69
--- /dev/null
+++ b/sabel/cache/File.php
@@ -0,0 +1,93 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cache_File implements Sabel_Cache_Interface
+{
+ private static $instances = array();
+
+ protected $dir = "";
+
+ private function __construct($dir)
+ {
+ if (is_dir($dir)) {
+ $this->dir = $dir;
+ } else {
+ $message = __METHOD__ . "() {$dir}";
+ throw new Sabel_Exception_DirectoryNotFound($message);
+ }
+ }
+
+ public static function create($dir = "")
+ {
+ if (empty($dir)) {
+ if (defined("CACHE_DIR_PATH")) {
+ $dir = CACHE_DIR_PATH;
+ } else {
+ $message = __METHOD__ . "() CACHE_DIR_PATH not defined.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ if (isset(self::$instances[$dir])) {
+ return self::$instances[$dir];
+ }
+
+ return self::$instances[$dir] = new self($dir);
+ }
+
+ public function read($key)
+ {
+ $result = null;
+
+ $path = $this->getPath($key);
+
+ if (is_readable($path)) {
+ $data = @unserialize(file_get_contents($path));
+
+ if ($data !== false) {
+ if ($data["timeout"] !== 0 && time() >= $data["timeout"]) {
+ unlink($path);
+ } else {
+ $result = $data["value"];
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+ $data = array("value" => $value);
+
+ if ($timeout !== 0) {
+ $timeout = time() + $timeout;
+ }
+
+ $data["timeout"] = $timeout;
+ file_put_contents($this->getPath($key), serialize($data), LOCK_EX);
+ }
+
+ public function delete($key)
+ {
+ $result = $this->read($key);
+
+ $path = $this->getPath($key);
+ if (is_file($path)) unlink($path);
+
+ return $result;
+ }
+
+ protected function getPath($key)
+ {
+ return $this->dir . DS . $key . ".cache";
+ }
+}
diff --git a/sabel/cache/Interface.php b/sabel/cache/Interface.php
new file mode 100755
index 0000000..c0d6f86
--- /dev/null
+++ b/sabel/cache/Interface.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Cache_Interface
+{
+ public function read($key);
+ public function write($key, $value, $timeout = 0);
+ public function delete($key);
+}
diff --git a/sabel/cache/Manager.php b/sabel/cache/Manager.php
new file mode 100755
index 0000000..09754ef
--- /dev/null
+++ b/sabel/cache/Manager.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cache_Manager
+{
+ private static $support = array();
+ private static $initialized = false;
+
+ public static function init()
+ {
+ if (self::$initialized) return;
+
+ self::$support["apc"] = extension_loaded("apc");
+ self::$support["memcache"] = extension_loaded("memcache");
+
+ self::$initialized = true;
+ }
+
+ public static function getUsableCache()
+ {
+ self::init();
+
+ $instance = null;
+
+ if (!defined("ENVIRONMENT") || ENVIRONMENT !== PRODUCTION) {
+ $instance = Sabel_Cache_Null::create();
+ } elseif (self::$support["apc"]) {
+ $instance = Sabel_Cache_Apc::create();
+ } elseif (self::$support["memcache"]) {
+ $instance = Sabel_Cache_Memcache::create();
+ } else {
+ $instance = Sabel_Cache_File::create();
+ }
+
+ return $instance;
+ }
+}
diff --git a/sabel/cache/Memcache.php b/sabel/cache/Memcache.php
new file mode 100755
index 0000000..022ed9f
--- /dev/null
+++ b/sabel/cache/Memcache.php
@@ -0,0 +1,59 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cache_Memcache implements Sabel_Cache_Interface
+{
+ private static $instances = array();
+
+ /**
+ * @var Sabel_Kvs_Memcache
+ */
+ protected $kvs = null;
+
+ private function __construct($host, $port)
+ {
+ if (extension_loaded("memcache")) {
+ $this->kvs = Sabel_Kvs_Memcache::create($host, $port);
+ } else {
+ $message = __METHOD__ . "() memcache extension not loaded.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public static function create($host = "localhost", $port = 11211)
+ {
+ if (isset(self::$instances[$host][$port])) {
+ return self::$instances[$host][$port];
+ }
+
+ return self::$instances[$host][$port] = new self($host, $port);
+ }
+
+ public function addServer($host, $port = 11211, $weight = 1)
+ {
+ $this->kvs->addServer($host, $port, true, $weight);
+ }
+
+ public function read($key)
+ {
+ return $this->kvs->read($key);
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+ $this->kvs->write($key, $value, $timeout);
+ }
+
+ public function delete($key)
+ {
+ return $this->kvs->delete($key);
+ }
+}
diff --git a/sabel/cache/Null.php b/sabel/cache/Null.php
new file mode 100755
index 0000000..10cc447
--- /dev/null
+++ b/sabel/cache/Null.php
@@ -0,0 +1,39 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cache_Null implements Sabel_Cache_Interface
+{
+ private static $instance = null;
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function read($key)
+ {
+ return null;
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+
+ }
+
+ public function delete($key)
+ {
+ return null;
+ }
+}
diff --git a/sabel/container/Aspect.php b/sabel/container/Aspect.php
new file mode 100755
index 0000000..099e791
--- /dev/null
+++ b/sabel/container/Aspect.php
@@ -0,0 +1,54 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+final class Sabel_Container_Aspect
+{
+ private $className = "";
+ private $adviceClass = "";
+ private $annotatedClasses = array();
+
+ public function __construct($className)
+ {
+ $this->className = $className;
+ }
+
+ public function getName()
+ {
+ return $this->className;
+ }
+
+ public function advice($adviceClass)
+ {
+ $this->adviceClass = $adviceClass;
+ return $this;
+ }
+
+ public function getAdvice()
+ {
+ return $this->adviceClass;
+ }
+
+ public function annotate($className, array $intercepters)
+ {
+ $this->annotatedClasses[$className] = $intercepters;
+ return $this;
+ }
+
+ public function hasAnnotated()
+ {
+ return (count($this->annotatedClasses) >= 1);
+ }
+
+ public function getAnnotated()
+ {
+ return $this->annotatedClasses;
+ }
+}
\ No newline at end of file
diff --git a/sabel/container/Bind.php b/sabel/container/Bind.php
new file mode 100755
index 0000000..19824a0
--- /dev/null
+++ b/sabel/container/Bind.php
@@ -0,0 +1,52 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+final class Sabel_Container_Bind
+{
+ private
+ $interface,
+ $setter,
+ $implementation = "";
+
+ public function __construct($interface)
+ {
+ $this->interface = $interface;
+ }
+
+ public function to($implementation)
+ {
+ $this->implementation = $implementation;
+
+ return $this;
+ }
+
+ public function setter($methodName)
+ {
+ $this->setter = trim($methodName);
+
+ return $this;
+ }
+
+ public function hasSetter()
+ {
+ return !empty($this->setter);
+ }
+
+ public function getSetter()
+ {
+ return $this->setter;
+ }
+
+ public function getImplementation()
+ {
+ return $this->implementation;
+ }
+}
diff --git a/sabel/container/CompositeConfig.php b/sabel/container/CompositeConfig.php
new file mode 100755
index 0000000..e70547f
--- /dev/null
+++ b/sabel/container/CompositeConfig.php
@@ -0,0 +1,27 @@
+configs = array();
+ }
+
+ public function add(Sabel_Container_Injection $config)
+ {
+ $config->configure();
+ $this->configs[] = $config;
+ }
+
+ public function configure()
+ {
+ foreach ($this->configs as $config) {
+ $this->binds = array_merge($this->binds, $config->binds);
+ $this->aspects = array_merge($this->aspects, $config->aspects);
+ $this->lifecycle = array_merge($this->lifecycle, $config->lifecycle);
+ $this->constructs = array_merge($this->constructs, $config->constructs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/sabel/container/Construct.php b/sabel/container/Construct.php
new file mode 100755
index 0000000..b5f99b8
--- /dev/null
+++ b/sabel/container/Construct.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Container_Construct
+{
+ private $constructs = array();
+ private $source = "";
+
+ public function __construct($className)
+ {
+ $this->source = $className;
+ }
+
+ public function with($className)
+ {
+ $this->constructs[] = $className;
+
+ return $this;
+ }
+
+ public function hasConstruct()
+ {
+ return (count($this->constructs) >= 1);
+ }
+
+ public function getConstructs()
+ {
+ return $this->constructs;
+ }
+}
\ No newline at end of file
diff --git a/sabel/container/DefaultInjection.php b/sabel/container/DefaultInjection.php
new file mode 100755
index 0000000..2fcf88e
--- /dev/null
+++ b/sabel/container/DefaultInjection.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Container_Injection implements Sabel_Config
+{
+ protected
+ $binds = array(),
+ $aspects = array(),
+ $lifecycle = array(),
+ $constructs = array();
+
+ /**
+ * bind interface to implementation
+ *
+ * @param string $interface name of interface
+ * @return Sabel_Container_Bind
+ */
+ public function bind($interface)
+ {
+ $bind = new Sabel_Container_Bind($interface);
+ $this->binds[$interface][] = $bind;
+
+ return $bind;
+ }
+
+ /**
+ * bind constructer with object or value
+ *
+ * @param string $interface name of interface
+ * @return Sabel_Container_Bind
+ */
+ public function construct($className)
+ {
+ $construct = new Sabel_Container_Construct($className);
+ $this->constructs[$className] = $construct;
+
+ return $construct;
+ }
+
+ /**
+ * bind aspect
+ *
+ * @param string $interface name of interface
+ * @return Sabel_Container_Bind
+ */
+ public function aspect($className)
+ {
+ $aspect = new Sabel_Container_Aspect($className);
+ $this->aspects[$className] = $aspect;
+
+ return $aspect;
+ }
+
+ public function getAspect($className)
+ {
+ if ($this->hasAspect($className)) {
+ return $this->aspects[$className];
+ } else {
+ return false;
+ }
+ }
+
+ public function getAspects()
+ {
+ if (is_array($this->aspects)) {
+ return array_values($this->aspects);
+ } else {
+ return array();
+ }
+ }
+
+ public function hasAspect($className)
+ {
+ return isset($this->aspects[$className]);
+ }
+
+ public function hasConstruct($className)
+ {
+ return array_key_exists($className, $this->constructs);
+ }
+
+ public function getConstruct($className)
+ {
+ if (isset($this->constructs[$className])) {
+ return $this->constructs[$className];
+ } else {
+ return false;
+ }
+ }
+
+ public function getBinds()
+ {
+ return $this->binds;
+ }
+
+ public function hasBinds()
+ {
+ return (count($this->binds) >= 1);
+ }
+
+ public function hasBind($className)
+ {
+ return isset($this->binds[$className]);
+ }
+
+ public function getBind($className)
+ {
+ if ($this->hasBind($className)) {
+ return $this->binds[$className];
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/sabel/container/exception/InvalidArgument.php b/sabel/container/exception/InvalidArgument.php
new file mode 100755
index 0000000..3b1acce
--- /dev/null
+++ b/sabel/container/exception/InvalidArgument.php
@@ -0,0 +1,3 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Controller_Page extends Sabel_Object
+{
+ /**
+ * @var Sabel_Request
+ */
+ protected $request = null;
+
+ /**
+ * @var Sabel_Response
+ */
+ protected $response = null;
+
+ /**
+ * @var Sabel_Response_Redirector
+ */
+ protected $redirect = null;
+
+ /**
+ * @var Sabel_Session
+ */
+ protected $session = null;
+
+ /**
+ * @var object[]
+ */
+ protected $mixins = array();
+
+ /**
+ * @var string
+ */
+ protected $action = "";
+
+ /**
+ * @var boolean
+ */
+ protected $executed = false;
+
+ /**
+ * @var array
+ */
+ protected $hidden = array();
+
+ /**
+ * @var array
+ */
+ protected $attributes = array();
+
+ /**
+ * initialize a controller.
+ * execute ones before action execute.
+ */
+ public function initialize()
+ {
+
+ }
+
+ public function finalize()
+ {
+
+ }
+
+ /**
+ * @param object $object
+ */
+ public function mixin($className)
+ {
+ if (is_string($className)) {
+ $instance = new $className();
+ } elseif (is_object($className)) {
+ $instance = $className;
+ $className = get_class($instance);
+ }
+
+ if ($instance instanceof self) {
+ $properties = array("request", "response", "redirect", "session");
+ foreach ($properties as $property) {
+ $instance->$property = $this->$property;
+ }
+ } elseif (method_exists($instance, "setController")) {
+ $instance->setController($this);
+ }
+
+ $reflection = new ReflectionClass($instance);
+ $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
+ foreach ($methods as $method) {
+ if ($method->getDeclaringClass()->name === $className) {
+ $this->mixins[$method->name] = $instance;
+ }
+ }
+ }
+
+ /**
+ * @param string $method
+ * @param array $arguments
+ */
+ public function __call($method, $arguments)
+ {
+ if (isset($this->mixins[$method])) {
+ return call_user_func_array(array($this->mixins[$method], $method), $arguments);
+ } else {
+ $message = "Call to undefined method " . __CLASS__ . "::{$method}()";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ /**
+ * @param Sabel_Request $request
+ *
+ * @return void
+ */
+ public function setRequest(Sabel_Request $request)
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * @return Sabel_Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * @param Sabel_Response $response
+ *
+ * @return void
+ */
+ public function setResponse(Sabel_Response $response)
+ {
+ $this->response = $response;
+ $this->redirect = $response->getRedirector();
+ }
+
+ /**
+ * @return Sabel_Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * @param Sabel_Session_Abstract $session
+ *
+ * @return void
+ */
+ public function setSession(Sabel_Session_Abstract $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * @return Sabel_Session_Abstract
+ */
+ public function getSession()
+ {
+ return $this->session;
+ }
+
+ /**
+ * execute action
+ *
+ * @access public
+ * @param string $action action method name
+ * @return mixed result of execute an action.
+ */
+ public function execute($action = null, $params = array())
+ {
+ if ($action === null) {
+ $action = $this->action;
+ }
+
+ if ($this->isReserved($action) || $this->isHiddenAction($action)) {
+ $this->response->getStatus()->setCode(Sabel_Response::NOT_FOUND);
+ } elseif ($this->isValidAction($action)) {
+ if (count($params) >= 1) {
+ call_user_func_array(array($this, $action), $params);
+ } else {
+ $this->$action();
+ }
+
+ $this->executed = true;
+ }
+
+ return $this;
+ }
+
+ public function isExecuted()
+ {
+ return $this->executed;
+ }
+
+ public function isRedirected()
+ {
+ return $this->redirect->isRedirected();
+ }
+
+ /**
+ * @param string $action
+ *
+ * @return boolean
+ */
+ protected function isReserved($action)
+ {
+ static $reserved = array();
+
+ if (empty($reserved)) {
+ $reserved = get_class_methods(__CLASS__);
+ }
+
+ return in_array($action, $reserved, true);
+ }
+
+ /**
+ * @param string $action
+ *
+ * @return boolean
+ */
+ protected function isHiddenAction($action)
+ {
+ return in_array($action, $this->hidden, true);
+ }
+
+ /**
+ * @param string $action
+ *
+ * @return boolean
+ */
+ protected function isValidAction($action)
+ {
+ if (!$this->hasMethod($action)) return false;
+
+ $method = new ReflectionMethod($this->getName(), $action);
+ return $method->isPublic();
+ }
+
+ public function getAttribute($name)
+ {
+ if (array_key_exists($name, $this->attributes)) {
+ return $this->attributes[$name];
+ } else {
+ return null;
+ }
+ }
+
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function __get($name)
+ {
+ return $this->getAttribute($name);
+ }
+
+ public function __set($name, $value)
+ {
+ $this->setAttribute($name, $value);
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function setAttributes($attributes)
+ {
+ $this->attributes = array_merge($this->attributes, $attributes);
+ }
+
+ public function hasAttribute($name)
+ {
+ return array_key_exists($name, $this->attributes);
+ }
+
+ public function isAttributeSet($name)
+ {
+ return isset($this->attributes[$name]);
+ }
+
+ public function assign($name, $value)
+ {
+ $this->response->setResponse($name, $value);
+ }
+
+ public final function setAction($action)
+ {
+ if (is_string($action)) {
+ $this->action = $action;
+ } else {
+ $message = __METHOD__ . "() action name must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ public final function getAction()
+ {
+ return $this->action;
+ }
+}
diff --git a/sabel/cookie/Abstract.php b/sabel/cookie/Abstract.php
new file mode 100755
index 0000000..e51c178
--- /dev/null
+++ b/sabel/cookie/Abstract.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Cookie_Abstract extends Sabel_Object
+{
+ public function delete($key, $options = array())
+ {
+ $options["expire"] = time() - 3600;
+ $this->set($key, "", $options);
+ }
+
+ protected function createOptions(array $options)
+ {
+ if (!isset($options["expire"])) $options["expire"] = time() + 86400;
+ if (!isset($options["path"])) $options["path"] = "/";
+ if (!isset($options["domain"])) $options["domain"] = "";
+ if (!isset($options["secure"])) $options["secure"] = false;
+ if (!isset($options["httpOnly"])) $options["httpOnly"] = false;
+
+ return $options;
+ }
+}
diff --git a/sabel/cookie/Factory.php b/sabel/cookie/Factory.php
new file mode 100755
index 0000000..a1948ab
--- /dev/null
+++ b/sabel/cookie/Factory.php
@@ -0,0 +1,22 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cookie_Factory
+{
+ public static function create()
+ {
+ if (is_cli()) {
+ return Sabel_Cookie_InMemory::create();
+ } else {
+ return Sabel_Cookie_Http::create();
+ }
+ }
+}
diff --git a/sabel/cookie/Http.php b/sabel/cookie/Http.php
new file mode 100755
index 0000000..c4e7455
--- /dev/null
+++ b/sabel/cookie/Http.php
@@ -0,0 +1,64 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cookie_Http extends Sabel_Cookie_Abstract
+{
+ private static $instance = null;
+
+ private function __construct()
+ {
+
+ }
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function set($key, $value, $options = array())
+ {
+ $options = $this->createOptions($options);
+
+ setcookie($key,
+ $value,
+ $options["expire"],
+ $options["path"],
+ $options["domain"],
+ $options["secure"],
+ $options["httpOnly"]);
+ }
+
+ public function rawset($key, $value, $options = array())
+ {
+ $options = $this->createOptions($options);
+
+ setrawcookie($key,
+ $value,
+ $options["expire"],
+ $options["path"],
+ $options["domain"],
+ $options["secure"],
+ $options["httpOnly"]);
+ }
+
+ public function get($key)
+ {
+ if (array_key_exists($key, $_COOKIE)) {
+ return $_COOKIE[$key];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/sabel/cookie/InMemory.php b/sabel/cookie/InMemory.php
new file mode 100755
index 0000000..014c93f
--- /dev/null
+++ b/sabel/cookie/InMemory.php
@@ -0,0 +1,92 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Cookie_InMemory extends Sabel_Cookie_Abstract
+{
+ private static $instance = null;
+
+ protected $cookies = array();
+
+ private function __construct()
+ {
+
+ }
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function set($key, $value, $options = array())
+ {
+ $options = $this->createOptions($options);
+
+ $this->cookies[$key] = array("value" => urlencode($value),
+ "expire" => $options["expire"],
+ "path" => $options["path"],
+ "domain" => $options["domain"],
+ "secure" => $options["secure"],
+ "httpOnly" => $options["httpOnly"]);
+ }
+
+ public function rawset($key, $value, $options = array())
+ {
+ $options = $this->createOptions($options);
+
+ $this->cookies[$key] = array("value" => $value,
+ "expire" => $options["expire"],
+ "path" => $options["path"],
+ "domain" => $options["domain"],
+ "secure" => $options["secure"],
+ "httpOnly" => $options["httpOnly"]);
+ }
+
+ public function get($key)
+ {
+ if (array_key_exists($key, $this->cookies)) {
+ $cookie = $this->cookies[$key];
+
+ if ($cookie["expire"] < time()) {
+ return null;
+ }
+
+ $path = $cookie["path"];
+ if ($path === "/") return $cookie["value"];
+
+ $uri = $this->getRequestUri();
+ if (strpos($uri, $path) === 0) {
+ return $cookie["value"];
+ }
+ }
+
+ return null;
+ }
+
+ protected function getRequestUri()
+ {
+ if (class_exists("Sabel_Context", false)) {
+ $bus = Sabel_Context::getContext()->getBus();
+ if (is_object($bus) && ($request = $bus->get("request"))) {
+ return "/" . $request->getUri();
+ }
+ }
+
+ if (isset($_SERVER["REQUEST_URI"])) {
+ return "/" . normalize_uri($_SERVER["REQUEST_URI"]);
+ } else {
+ return "/";
+ }
+ }
+}
diff --git a/sabel/db/Condition.php b/sabel/db/Condition.php
new file mode 100755
index 0000000..31abc01
--- /dev/null
+++ b/sabel/db/Condition.php
@@ -0,0 +1,90 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition
+{
+ const EQUAL = 1;
+ const ISNULL = 2;
+ const ISNOTNULL = 3;
+ const IN = 4;
+ const BETWEEN = 5;
+ const LIKE = 6;
+ const GREATER_EQUAL = 7;
+ const GREATER_THAN = 8;
+ const LESS_EQUAL = 9;
+ const LESS_THAN = 10;
+ const DIRECT = 11;
+
+ /**
+ * @param const $type Sabel_Db_Condition
+ * @param string $column
+ * @param mixed $value
+ * @param boolean $not
+ *
+ * @return Sabel_Db_Abstract_Condition
+ */
+ public static function create($type, $column, $value = null, $not = false)
+ {
+ switch ($type) {
+ case self::EQUAL:
+ $condition = new Sabel_Db_Condition_Equal($column);
+ break;
+
+ case self::BETWEEN:
+ $condition = new Sabel_Db_Condition_Between($column);
+ break;
+
+ case self::IN:
+ $condition = new Sabel_Db_Condition_In($column);
+ break;
+
+ case self::LIKE:
+ $condition = new Sabel_Db_Condition_Like($column);
+ break;
+
+ case self::ISNULL:
+ $condition = new Sabel_Db_Condition_IsNull($column);
+ break;
+
+ case self::ISNOTNULL:
+ $condition = new Sabel_Db_Condition_IsNotNull($column);
+ break;
+
+ case self::GREATER_EQUAL:
+ $condition = new Sabel_Db_Condition_GreaterEqual($column);
+ break;
+
+ case self::LESS_EQUAL:
+ $condition = new Sabel_Db_Condition_LessEqual($column);
+ break;
+
+ case self::GREATER_THAN:
+ $condition = new Sabel_Db_Condition_GreaterThan($column);
+ break;
+
+ case self::LESS_THAN:
+ $condition = new Sabel_Db_Condition_LessThan($column);
+ break;
+
+ case self::DIRECT:
+ $condition = new Sabel_Db_Condition_Direct($column);
+ break;
+
+ default:
+ $message = __METHOD__ . "() invalid condition type.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $condition->setValue($value)->isNot($not);
+
+ return $condition;
+ }
+}
diff --git a/sabel/db/Config.php b/sabel/db/Config.php
new file mode 100755
index 0000000..a769f3c
--- /dev/null
+++ b/sabel/db/Config.php
@@ -0,0 +1,100 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Config
+{
+ private static $initialized = false;
+ private static $configs = array();
+
+ public static function initialize(Sabel_Config $config)
+ {
+ if (self::$initialized) return;
+
+ foreach ($config->configure() as $connectionName => $params) {
+ self::$configs[$connectionName] = $params;
+ }
+
+ self::$initialized = true;
+ }
+
+ public static function add($connectionName, $params)
+ {
+ self::$configs[$connectionName] = $params;
+ }
+
+ public static function get($connectionName = null)
+ {
+ if ($connectionName === null) {
+ return self::$configs;
+ } else {
+ return self::getConfig($connectionName);
+ }
+ }
+
+ public static function getPackage($connectionName)
+ {
+ $config = self::getConfig($connectionName);
+
+ if (isset($config["package"])) {
+ return $config["package"];
+ } else {
+ $message = "'package' not found in config.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+
+ public static function getSchemaName($connectionName)
+ {
+ // @todo improvement
+
+ $package = self::getPackage($connectionName);
+ $ignores = array("sabel.db.pdo.sqlite" => 1, "sabel.db.ibase" => 1);
+ if (isset($ignores[$package])) return null;
+
+ $config = self::getConfig($connectionName);
+
+ if (isset($config["schema"])) {
+ return $config["schema"];
+ } elseif (strpos($package, "mysql") !== false) {
+ return $config["database"];
+ } elseif (strpos($package, "pgsql") !== false) {
+ return "public";
+ } elseif (strpos($package, "oci") !== false) {
+ return strtoupper($config["user"]);
+ } elseif (strpos($package, "mssql") !== false) {
+ return "dbo";
+ } else {
+ $message = __METHOD__ . "() 'schema' not found in config.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+
+ public static function getConnectionNamesOfSameSetting($connectionName)
+ {
+ $names = array();
+ foreach (self::$configs as $name => $setting) {
+ if ($name === $connectionName) continue;
+ if ($setting == self::$configs[$connectionName]) $names[] = $name;
+ }
+
+ return $names;
+ }
+
+ private static function getConfig($connectionName)
+ {
+ if (isset(self::$configs[$connectionName])) {
+ return self::$configs[$connectionName];
+ } else {
+ $message = "getConfig() config for '{$connectionName}' not found.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+}
diff --git a/sabel/db/Connection.php b/sabel/db/Connection.php
new file mode 100755
index 0000000..5c7e4f7
--- /dev/null
+++ b/sabel/db/Connection.php
@@ -0,0 +1,100 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Connection
+{
+ /**
+ * @var resource[]
+ */
+ private static $connections = array();
+
+ /**
+ * @param Sabel_Db_Driver $driver
+ *
+ * @throws Sabel_Db_Exception_Connection
+ * @return void
+ */
+ public static function connect(Sabel_Db_Driver $driver)
+ {
+ if (Sabel_Db_Transaction::isActive()) {
+ $connectionName = $driver->getConnectionName();
+ if ($connection = Sabel_Db_Transaction::getConnection($connectionName)) {
+ $driver->setConnection($connection);
+ } else {
+ Sabel_Db_Transaction::begin(self::_connect($driver));
+ }
+
+ $driver->autoCommit(false);
+ } else {
+ self::_connect($driver);
+ }
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @return void
+ */
+ public static function close($connectionName)
+ {
+ if (!isset(self::$connections[$connectionName])) return;
+
+ $conn = self::$connections[$connectionName];
+ Sabel_Db::createDriver($connectionName)->close($conn);
+
+ unset(self::$connections[$connectionName]);
+ }
+
+ /**
+ * @return void
+ */
+ public static function closeAll()
+ {
+ foreach (Sabel_Db_Config::get() as $connectionName => $config) {
+ self::close($connectionName);
+ }
+
+ self::$connections = array();
+ }
+
+ /**
+ * @param Sabel_Db_Driver $driver
+ *
+ * @throws Sabel_Db_Exception_Connection
+ * @return Sabel_Db_Driver
+ */
+ protected static function _connect(Sabel_Db_Driver $driver)
+ {
+ $connectionName = $driver->getConnectionName();
+ $names = Sabel_Db_Config::getConnectionNamesOfSameSetting($connectionName);
+
+ foreach ($names as $name) {
+ if (isset(self::$connections[$name])) {
+ $driver->setConnection(self::$connections[$name]);
+ return $driver;
+ }
+ }
+
+ if (!isset(self::$connections[$connectionName])) {
+ $result = $driver->connect(Sabel_Db_Config::get($connectionName));
+
+ if (is_string($result)) {
+ throw new Sabel_Db_Exception_Connection($result);
+ } else {
+ self::$connections[$connectionName] = $result;
+ }
+ }
+
+ $driver->setConnection(self::$connections[$connectionName]);
+
+ return $driver;
+ }
+}
diff --git a/sabel/db/Driver.php b/sabel/db/Driver.php
new file mode 100755
index 0000000..522b4b6
--- /dev/null
+++ b/sabel/db/Driver.php
@@ -0,0 +1,110 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Driver extends Sabel_Object
+{
+ const TRANS_READ_UNCOMMITTED = 1;
+ const TRANS_READ_COMMITTED = 2;
+ const TRANS_REPEATABLE_READ = 3;
+ const TRANS_SERIALIZABLE = 4;
+
+ /**
+ * @var string
+ */
+ protected $connectionName = "";
+
+ /**
+ * @var boolean
+ */
+ protected $autoCommit = true;
+
+ /**
+ * @var resource
+ */
+ protected $connection = null;
+
+ /**
+ * @var int
+ */
+ protected $affectedRows = 0;
+
+ abstract public function connect(array $params);
+ abstract public function begin($isolationLevel = null);
+ abstract public function commit();
+ abstract public function rollback();
+ abstract public function execute($sql, $bindParams = array(), $additionalParameters = array());
+ abstract public function getLastInsertId();
+ abstract public function close($connection);
+
+ public function __construct($connectionName)
+ {
+ $this->connectionName = $connectionName;
+ }
+
+ public function getConnectionName()
+ {
+ return $this->connectionName;
+ }
+
+ public function setConnection($connection)
+ {
+ $this->connection = $connection;
+ }
+
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ public function autoCommit($bool)
+ {
+ $this->autoCommit = $bool;
+ }
+
+ public function getAffectedRows()
+ {
+ return $this->affectedRows;
+ }
+
+ public function setTransactionIsolationLevel($level)
+ {
+ switch ($level) {
+ case self::TRANS_READ_UNCOMMITTED:
+ $query = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
+ break;
+ case self::TRANS_READ_COMMITTED:
+ $query = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";
+ break;
+ case self::TRANS_REPEATABLE_READ:
+ $query = "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ";
+ break;
+ case self::TRANS_SERIALIZABLE:
+ $query = "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE";
+ break;
+ default:
+ throw new Sabel_Exception_InvalidArgument("invalid isolation level.");
+ }
+
+ $this->execute($query);
+ }
+
+ protected function bind($sql, $bindParam)
+ {
+ if (empty($bindParam)) return $sql;
+
+ if (in_array(null, $bindParam, true)) {
+ array_walk($bindParam, create_function('&$val', 'if ($val === null) $val = "NULL";'));
+ }
+
+ return strtr($sql, $bindParam);
+ }
+}
diff --git a/sabel/db/Exception.php b/sabel/db/Exception.php
new file mode 100755
index 0000000..2f608c9
--- /dev/null
+++ b/sabel/db/Exception.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Exception extends Sabel_Exception_Runtime
+{
+
+}
diff --git a/sabel/db/Finder.php b/sabel/db/Finder.php
new file mode 100755
index 0000000..74068d8
--- /dev/null
+++ b/sabel/db/Finder.php
@@ -0,0 +1,317 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Finder
+{
+ protected $model = null;
+ protected $join = null;
+ protected $projection = null;
+
+ public function __construct($mdlName, $projection = null)
+ {
+ $this->model = (is_model($mdlName)) ? $mdlName : MODEL($mdlName);
+
+ if ($projection !== null) {
+ $this->p($projection);
+ }
+ }
+
+ public function getRawInstance()
+ {
+ return ($this->join === null) ? $this->model : $this->join;
+ }
+
+ public function p($projection)
+ {
+ $this->projection = $projection;
+
+ return $this;
+ }
+
+ public function eq($column, $value)
+ {
+ $this->model->setCondition(eq($column, $value));
+
+ return $this;
+ }
+
+ public function neq($column, $value)
+ {
+ $this->model->setCondition(neq($column, $value));
+
+ return $this;
+ }
+
+ public function in($column, array $values)
+ {
+ $this->model->setCondition(in($column, $values));
+
+ return $this;
+ }
+
+ public function nin($column, array $values)
+ {
+ $this->model->setCondition(nin($column, $values));
+
+ return $this;
+ }
+
+ public function lt($column, $value)
+ {
+ $this->model->setCondition(lt($column, $value));
+
+ return $this;
+ }
+
+ public function le($column, $value)
+ {
+ $this->model->setCondition(le($column, $value));
+
+ return $this;
+ }
+
+ public function gt($column, $value)
+ {
+ $this->model->setCondition(gt($column, $value));
+
+ return $this;
+ }
+
+ public function ge($column, $value)
+ {
+ $this->model->setCondition(ge($column, $value));
+
+ return $this;
+ }
+
+ public function between($column, $from, $to = null)
+ {
+ $this->model->setCondition(bw($column, $from, $to));
+
+ return $this;
+ }
+
+ public function bw($column, $from, $to = null)
+ {
+ return $this->between($column, $from, $to);
+ }
+
+ public function notBetween($column, $from, $to = null)
+ {
+ $this->model->setCondition(nbw($column, $from, $to));
+
+ return $this;
+ }
+
+ public function nbw($column, $from, $to = null)
+ {
+ return $this->notBetween($column, $from, $to);
+ }
+
+ public function starts($column, $value)
+ {
+ $this->model->setCondition(starts($column, $value));
+
+ return $this;
+ }
+
+ public function ends($column, $value)
+ {
+ $this->model->setCondition(ends($column, $value));
+
+ return $this;
+ }
+
+ public function contains($column, $value)
+ {
+ $this->model->setCondition(contains($column, $value));
+
+ return $this;
+ }
+
+ public function isNull($column)
+ {
+ $this->model->setCondition(isNull($column));
+
+ return $this;
+ }
+
+ public function isNotNull($column)
+ {
+ $this->model->setCondition(isNotNull($column));
+
+ return $this;
+ }
+
+ public function where(/* args */)
+ {
+ foreach (func_get_args() as $condition) {
+ $this->model->setCondition($condition);
+ }
+
+ return $this;
+ }
+
+ public function w(/* args */)
+ {
+ $args = func_get_args();
+ return call_user_func_array(array($this, "where"), $args);
+ }
+
+ public function orWhere(/* args */)
+ {
+ $or = new Sabel_Db_Condition_Or();
+ foreach (func_get_args() as $condition) {
+ $or->add($condition);
+ }
+
+ $this->model->setCondition($or);
+
+ return $this;
+ }
+
+ public function ow(/* args */)
+ {
+ $args = func_get_args();
+ return call_user_func_array(array($this, "orWhere"), $args);
+ }
+
+ public function andWhere(/* args */)
+ {
+ $and = new Sabel_Db_Condition_And();
+ foreach (func_get_args() as $condition) {
+ $and->add($condition);
+ }
+
+ $this->model->setCondition($and);
+
+ return $this;
+ }
+
+ public function aw(/* args */)
+ {
+ $args = func_get_args();
+ return call_user_func_array(array($this, "andWhere"), $args);
+ }
+
+ public function innerJoin($mdlName, $on = array(), $alias = "")
+ {
+ $this->_join($mdlName, $on, $alias, "INNER");
+
+ return $this;
+ }
+
+ public function leftJoin($mdlName, $on = array(), $alias = "")
+ {
+ $this->_join($mdlName, $on, $alias, "LEFT");
+
+ return $this;
+ }
+
+ public function rightJoin($mdlName, $on = array(), $alias = "")
+ {
+ $this->_join($mdlName, $on, $alias, "RIGHT");
+
+ return $this;
+ }
+
+ protected function _join($mdlName, $on, $alias, $type)
+ {
+ if ($this->join === null) {
+ $this->join = new Sabel_Db_Join($this->model);
+ }
+
+ $this->join->add($mdlName, $on, $alias, $type);
+ }
+
+ public function limit($limit)
+ {
+ $this->model->setLimit($limit);
+
+ return $this;
+ }
+
+ public function offset($offset)
+ {
+ $this->model->setOffset($offset);
+
+ return $this;
+ }
+
+ public function sort($column, $smode = "ASC", $nulls = "LAST")
+ {
+ $this->model->setOrderBy($column, $smode, $nulls);
+
+ return $this;
+ }
+
+ public function fetch()
+ {
+ $this->setProjection();
+
+ return $this->getRawInstance()->selectOne();
+ }
+
+ public function fetchAll()
+ {
+ $this->setProjection();
+
+ return $this->getRawInstance()->select();
+ }
+
+ public function fetchArray($column = null)
+ {
+ if ($this->join === null) {
+ $model = $this->model;
+
+ if ($column === null) {
+ $this->setProjection();
+ return $model->getRows();
+ } else {
+ $pkey = $model->getMetadata()->getPrimaryKey();
+ if (!is_array($pkey)) $pkey = array($pkey);
+
+ $projection = $pkey;
+ $projection[] = $column;
+
+ $model->setProjection($projection);
+ $rows = $model->getRows();
+
+ foreach ($rows as $idx => $row) {
+ $rows[$idx] = $row[$column];
+ }
+
+ return $rows;
+ }
+ } else {
+ $message = __METHOD__ . "() can't use fetchArray() on join select.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+
+ public function count()
+ {
+ $this->setProjection();
+
+ if ($this->join === null) {
+ return $this->model->getCount();
+ } else {
+ return $this->join->getCount(false);
+ }
+ }
+
+ protected function setProjection()
+ {
+ if ($this->projection !== null) {
+ $this->getRawInstance()->setProjection($this->projection);
+ }
+ }
+}
diff --git a/sabel/db/Join.php b/sabel/db/Join.php
new file mode 100755
index 0000000..92dbf57
--- /dev/null
+++ b/sabel/db/Join.php
@@ -0,0 +1,224 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join extends Sabel_Object
+{
+ /**
+ * @var Sabel_Db_Model
+ */
+ protected $model = null;
+
+ /**
+ * @var array
+ */
+ protected $projection = array();
+
+ /**
+ * @var string
+ */
+ protected $tblName = "";
+
+ /**
+ * @var object[]
+ */
+ protected $objects = array();
+
+ /**
+ * @var Sabel_Db_Join_Structure
+ */
+ protected $structure = null;
+
+ public function __construct($model)
+ {
+ if (is_string($model)) {
+ $model = MODEL($model);
+ } elseif (!is_model($model)) {
+ $message = __METHOD__ . "() argument must be a string or an instance of model.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $this->model = $model;
+ $this->tblName = $model->getTableName();
+ $this->structure = Sabel_Db_Join_Structure::getInstance();
+ }
+
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ public function clear()
+ {
+ if (is_object($this->structure)) {
+ $this->structure->clear();
+ }
+
+ Sabel_Db_Join_ColumnHash::clear();
+ }
+
+ public function setProjection(array $projections)
+ {
+ $this->projection = $projections;
+
+ return $this;
+ }
+
+ public function setCondition($arg1, $arg2 = null)
+ {
+ $this->model->setCondition($arg1, $arg2);
+
+ return $this;
+ }
+
+ public function setOrderBy($column, $mode = "asc", $nulls = "last")
+ {
+ $this->model->setOrderBy($column, $mode, $nulls);
+
+ return $this;
+ }
+
+ public function setLimit($limit)
+ {
+ $this->model->setLimit($limit);
+
+ return $this;
+ }
+
+ public function setOffset($offset)
+ {
+ $this->model->setOffset($offset);
+
+ return $this;
+ }
+
+ public function innerJoin($object, $on = array(), $alias = "")
+ {
+ return $this->add($object, $on, $alias, "INNER");
+ }
+
+ public function leftJoin($object, $on = array(), $alias = "")
+ {
+ return $this->add($object, $on, $alias, "LEFT");
+ }
+
+ public function rightJoin($object, $on = array(), $alias = "")
+ {
+ return $this->add($object, $on, $alias, "RIGHT");
+ }
+
+ public function add($object, $on = array(), $alias = "", $type = "")
+ {
+ if (is_string($object) || is_model($object)) {
+ $object = new Sabel_Db_Join_Object($object);
+ }
+
+ if (!empty($alias)) $object->setAlias($alias);
+ if (!empty($type)) $object->setJoinType($type);
+
+ $object->setChildName($this->tblName);
+
+ $this->structure->addJoinObject($this->tblName, $object);
+ $this->objects[] = $object;
+
+ if (!empty($on)) {
+ $object->on($on);
+ } elseif ($object->getOn() === array()) {
+ $object->on(create_join_key(
+ $this->model, $object->getModel()->getTableName()
+ ));
+ }
+
+ return $this;
+ }
+
+ public function getCount($clearState = true)
+ {
+ $stmt = $this->model->prepareStatement(Sabel_Db_Statement::SELECT);
+
+ $query = array();
+ foreach ($this->objects as $object) {
+ $query[] = $object->getJoinQuery($stmt);
+ }
+
+ $rows = $this->execute($stmt, "COUNT(*) AS cnt", implode("", $query));
+ if ($clearState) $this->clear();
+ return (int)$rows[0]["cnt"];
+ }
+
+ public function selectOne()
+ {
+ $results = $this->select();
+ return (isset($results[0])) ? $results[0] : null;
+ }
+
+ public function select()
+ {
+ $stmt = $this->model->prepareStatement(Sabel_Db_Statement::SELECT);
+ $projection = $this->createProjection($stmt);
+
+ $query = array();
+ foreach ($this->objects as $object) {
+ $query[] = $object->getJoinQuery($stmt);
+ }
+
+ $results = array();
+ if ($rows = $this->execute($stmt, $projection, implode("", $query))) {
+ $results = Sabel_Db_Join_Result::build($this->model, $this->structure, $rows);
+ }
+
+ $this->clear();
+
+ return $results;
+ }
+
+ protected function execute($stmt, $projection, $join)
+ {
+ $stmt->projection($projection)
+ ->where($this->model->getCondition()->build($stmt))
+ ->join($join);
+
+ $constraints = $this->model->getConstraints();
+ return $stmt->constraints($constraints)->execute();
+ }
+
+ protected function createProjection(Sabel_Db_Statement $stmt)
+ {
+ if (empty($this->projection)) {
+ $projection = array();
+ foreach ($this->objects as $object) {
+ $projection = array_merge($projection, $object->getProjection($stmt));
+ }
+
+ $quotedTblName = $stmt->quoteIdentifier($this->tblName);
+ foreach ($this->model->getColumnNames() as $column) {
+ $projection[] = $quotedTblName . "." . $stmt->quoteIdentifier($column);
+ }
+ } else {
+ $projection = array();
+ foreach ($this->projection as $name => $proj) {
+ if (($tblName = convert_to_tablename($name)) === $this->tblName) {
+ foreach ($proj as $column) {
+ $projection[] = $stmt->quoteIdentifier($tblName) . "." . $stmt->quoteIdentifier($column);
+ }
+ } else {
+ foreach ($proj as $column) {
+ $as = "{$tblName}.{$column}";
+ if (strlen($as) > 30) $as = Sabel_Db_Join_ColumnHash::toHash($as);
+ $p = $stmt->quoteIdentifier($tblName) . "." . $stmt->quoteIdentifier($column);
+ $projection[] = $p . " AS " . $stmt->quoteIdentifier($as);
+ }
+ }
+ }
+ }
+
+ return implode(", ", $projection);
+ }
+}
diff --git a/sabel/db/Metadata.php b/sabel/db/Metadata.php
new file mode 100755
index 0000000..9ebb494
--- /dev/null
+++ b/sabel/db/Metadata.php
@@ -0,0 +1,71 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Metadata
+{
+ private static $metadata = array();
+
+ public static function getTableInfo($tblName, $connectionName = "default")
+ {
+ if (isset(self::$metadata[$tblName])) {
+ return self::$metadata[$tblName];
+ }
+
+ if (self::schemaClassExists($tblName)) {
+ $cols = array();
+ $className = "Schema_" . convert_to_modelname($tblName);
+ $schemaClass = new $className();
+ foreach ($schemaClass->get() as $colName => $info) {
+ $co = new Sabel_Db_Metadata_Column();
+ $co->name = $colName;
+ foreach ($info as $key => $val) $co->$key = $val;
+ $cols[$colName] = $co;
+ }
+
+ $tblSchema = new Sabel_Db_Metadata_Table($tblName, $cols);
+ $properties = $schemaClass->getProperty();
+ $tblSchema->setTableEngine($properties["tableEngine"]);
+ $tblSchema->setUniques($properties["uniques"]);
+ $tblSchema->setForeignKeys($properties["fkeys"]);
+ } else {
+ $schemaObj = Sabel_Db::createMetadata($connectionName);
+ $tblSchema = $schemaObj->getTable($tblName);
+ }
+
+ return self::$metadata[$tblName] = $tblSchema;
+ }
+
+ public static function schemaClassExists($tblName)
+ {
+ $className = "Schema_" . convert_to_modelname($tblName);
+ return Sabel::using($className);
+ }
+
+ public static function getTableList($connectionName = "default")
+ {
+ $className = "Schema_" . ucfirst($connectionName) . "TableList";
+
+ if (Sabel::using($className)) {
+ $sc = new $className();
+ return $sc->get();
+ } else {
+ return Sabel_Db::createMetadata($connectionName)->getTableList();
+ }
+ }
+
+ public static function clear()
+ {
+ $metadata = self::$metadata;
+ self::$metadata = array();
+
+ return $metadata;
+ }
+}
diff --git a/sabel/db/Model.php b/sabel/db/Model.php
new file mode 100755
index 0000000..41e01bf
--- /dev/null
+++ b/sabel/db/Model.php
@@ -0,0 +1,997 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Model extends Sabel_Object
+{
+ /**
+ * @var string
+ */
+ protected $connectionName = "default";
+
+ /**
+ * @var string
+ */
+ protected $tableName = "";
+
+ /**
+ * @var string
+ */
+ protected $modelName = "";
+
+ /**
+ * @var Sabel_Db_Statement
+ */
+ protected $statement = null;
+
+ /**
+ * @var Sabel_Db_Metadata_Table
+ */
+ protected $metadata = null;
+
+ /**
+ * @var Sabel_Db_Metadata_Column[]
+ */
+ protected $metaCols = array();
+
+ /**
+ * @var array
+ */
+ protected $projection = array();
+
+ /**
+ * @var boolean
+ */
+ protected $selected = false;
+
+ /**
+ * @var array
+ */
+ protected $constraints = array();
+
+ /**
+ * @var Sabel_Db_Condition_Manager
+ */
+ protected $condition = null;
+
+ /**
+ * @var boolean
+ */
+ protected $autoReinit = true;
+
+ /**
+ * @var array
+ */
+ protected $primaryKeyValues = array();
+
+ /**
+ * @var array
+ */
+ protected $values = array();
+
+ /**
+ * @var array
+ */
+ protected $updateValues = array();
+
+ /**
+ * @var string
+ */
+ protected $versionColumn = "";
+
+ public function __construct($id = null)
+ {
+ $this->initialize();
+
+ if ($id !== null) {
+ $this->setCondition($id);
+ $this->_doSelectOne($this);
+ }
+ }
+
+ /**
+ * @param string $mdlName
+ *
+ * @return void
+ */
+ protected function initialize($mdlName = null)
+ {
+ if ($mdlName === null) {
+ $mdlName = get_class($this);
+ }
+
+ $this->modelName = $mdlName;
+
+ if ($this->tableName === "") {
+ $this->tableName = convert_to_tablename($mdlName);
+ }
+
+ $this->metadata = Sabel_Db_Metadata::getTableInfo($this->tableName, $this->connectionName);
+ $this->metaCols = $this->metadata->getColumns();
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @return void
+ */
+ public function setConnectionName($connectionName)
+ {
+ $this->connectionName = $connectionName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getConnectionName()
+ {
+ return $this->connectionName;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $val
+ *
+ * @return void
+ */
+ public function __set($key, $val)
+ {
+ $this->values[$key] = $val;
+ if ($this->selected) $this->updateValues[$key] = $val;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return void
+ */
+ public function unsetValue($key)
+ {
+ unset($this->values[$key]);
+ unset($this->updateValues[$key]);
+ }
+
+ /**
+ * @param array $values
+ *
+ * @return self
+ */
+ public function setValues(array $values)
+ {
+ foreach ($values as $key => $val) {
+ $this->__set($key, $val);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $values
+ *
+ * @return self
+ */
+ public function setUpdateValues(array $values)
+ {
+ $this->updateValues = $values;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (isset($this->values[$key])) {
+ $value = $this->values[$key];
+ if (isset($this->metaCols[$key])) {
+ return $this->metaCols[$key]->cast($value);
+ } else {
+ return $value;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getUpdateValues()
+ {
+ return $this->updateValues;
+ }
+
+ /**
+ * @param string $tblName
+ *
+ * @return void
+ */
+ public function setTableName($tblName)
+ {
+ $this->tableName = $tblName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTableName()
+ {
+ return $this->tableName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->modelName;
+ }
+
+ /**
+ * @return Sabel_Db_Metadata_Table
+ */
+ public function getMetadata()
+ {
+ return $this->metadata;
+ }
+
+ /**
+ * @return Sabel_Db_Metadata_Column[]
+ */
+ public function getColumns()
+ {
+ return $this->metaCols;
+ }
+
+ /**
+ * @return array
+ */
+ public function getColumnNames()
+ {
+ return array_keys($this->metaCols);
+ }
+
+ /**
+ * @return string
+ */
+ public function getVersionColumn()
+ {
+ $name = null;
+ if ($this->versionColumn !== "") {
+ $name = $this->versionColumn;
+ } elseif (isset($this->metaCols["version"])) {
+ $name = "version";
+ }
+
+ return $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ $columns = $this->metaCols;
+
+ $retValues = array();
+ foreach ($this->values as $k => $v) {
+ $retValues[$k] = (isset($columns[$k])) ? $columns[$k]->cast($v) : $v;
+ }
+
+ return $retValues;;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isSelected()
+ {
+ return $this->selected;
+ }
+
+ /**
+ * @param array $properties
+ *
+ * @return self
+ */
+ public function setProperties(array $properties)
+ {
+ $pkey = $this->metadata->getPrimaryKey();
+ $this->values = $properties;
+
+ if (empty($pkey)) return;
+
+ $this->selected = true;
+ if (is_string($pkey)) $pkey = (array)$pkey;
+
+ foreach ($pkey as $key) {
+ if (!isset($properties[$key])) {
+ $this->selected = false;
+ break;
+ }
+ }
+
+ if ($this->selected) {
+ foreach ($pkey as $key) {
+ $this->primaryKeyValues[$key] = $this->$key;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param boolean $bool
+ *
+ * @return void
+ */
+ public function autoReinit($bool)
+ {
+ $this->autoReinit = $bool;
+ }
+
+ /**
+ * @return void
+ */
+ public function clear()
+ {
+ $this->clearCondition(true);
+ $this->projection = array();
+ }
+
+ /**
+ * @param mixed $projection
+ *
+ * @return self
+ */
+ public function setProjection($projection)
+ {
+ $this->projection = $projection;
+
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getProjection()
+ {
+ return $this->projection;
+ }
+
+ /**
+ * @return Sabel_Db_Condition_Manager
+ */
+ public function getCondition()
+ {
+ if ($this->condition === null) {
+ $this->condition = new Sabel_Db_Condition_Manager();
+ }
+
+ return $this->condition;
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return self
+ */
+ public function setCondition($arg1, $arg2 = null)
+ {
+ if (empty($arg1)) return $this;
+
+ if ($arg1 instanceof Sabel_Db_Condition_Manager) {
+ $this->condition = $arg1;
+ } else {
+ $condition = $this->getCondition();
+
+ if ($arg2 !== null) {
+ $condition->create($arg1, $arg2);
+ } elseif (is_model($arg1)) {
+ $joinkey = create_join_key($this, $arg1->getTableName());
+ $colName = $this->getName() . "." . $joinkey["fkey"];
+ $condition->create($colName, $arg1->$joinkey["id"]);
+ } elseif (is_object($arg1)) {
+ $condition->add($arg1);
+ } else {
+ $colName = $this->getName() . "." . $this->metadata->getPrimaryKey();
+ $condition->create($colName, $arg1);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $column
+ * @param string $mode
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return self
+ */
+ public function setOrderBy($column, $mode = "asc", $nulls = "last")
+ {
+ if (is_string($column) || is_string($mode)) {
+ $this->constraints["order"][$column] = array(
+ "mode" => strtoupper($mode), "nulls" => $nulls
+ );
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param int $limit
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return self
+ */
+ public function setLimit($limit)
+ {
+ if (is_numeric($limit)) {
+ $this->constraints["limit"] = $limit;
+ } else {
+ $message = __METHOD__ . "() argument must be an integer.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param int $offset
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return self
+ */
+ public function setOffset($offset)
+ {
+ if (is_numeric($offset)) {
+ $this->constraints["offset"] = $offset;
+ } else {
+ $message = __METHOD__ . "() argument must be an integer.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getConstraints()
+ {
+ return $this->constraints;
+ }
+
+ /**
+ * @param boolean $andConstraints
+ *
+ * @return void
+ */
+ public function clearCondition($andConstraints = false)
+ {
+ if ($this->condition !== null) {
+ $this->condition->clear();
+ }
+
+ if ($andConstraints) $this->unsetConstraints();
+ }
+
+ /**
+ * @return void
+ */
+ public function unsetConstraints()
+ {
+ $this->constraints = array();
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return int
+ */
+ public function getCount($arg1 = null, $arg2 = null)
+ {
+ $result = null;
+ if ($this->hasMethod("beforeSelect")) {
+ $result = $this->beforeSelect("getCount");
+ }
+
+ if ($result === null) {
+ $this->setCondition($arg1, $arg2);
+ $projection = $this->projection;
+ $this->projection = "COUNT(*) AS cnt";
+
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::SELECT);
+ $rows = $this->prepareSelect($stmt)->execute();
+
+ $this->projection = $projection;
+ $result = (int)$rows[0]["cnt"];
+ }
+
+ if ($this->hasMethod("afterSelect")) {
+ $afterResult = $this->afterSelect($result, "getCount");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return int
+ */
+ public function getRows($arg1 = null, $arg2 = null)
+ {
+ $result = null;
+ if ($this->hasMethod("beforeSelect")) {
+ $result = $this->beforeSelect("getRows");
+ }
+
+ if ($result === null) {
+ $this->setCondition($arg1, $arg2);
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::SELECT);
+ $rows = $this->prepareSelect($stmt)->execute();
+
+ $result = array();
+ if (!empty($rows)) {
+ $pkey = $this->metadata->getPrimaryKey();
+
+ if (is_array($pkey)) {
+ foreach ($rows as $row) {
+ $ids = array();
+ foreach ($pkey as $key) $ids[] = $row[$key];
+ $result[implode(":", $ids)] = $row;
+ }
+ } else {
+ foreach ($rows as $row) {
+ $result[$row[$pkey]] = $row;
+ }
+ }
+ }
+ }
+
+ if ($this->hasMethod("afterSelect")) {
+ $afterResult = $this->afterSelect($result, "getRows");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return Sabel_Db_Model
+ */
+ public function selectOne($arg1 = null, $arg2 = null)
+ {
+ $result = null;
+ if ($this->hasMethod("beforeSelect")) {
+ $result = $this->beforeSelect("selectOne");
+ }
+
+ if ($result === null) {
+ if ($arg1 === null && $this->condition === null) {
+ $message = __METHOD__ . "() must set the condition.";
+ throw new Sabel_Db_Exception($message);
+ }
+
+ $this->setCondition($arg1, $arg2);
+ $model = MODEL($this->modelName);
+ $this->_doSelectOne($model);
+ $result = $model;
+ }
+
+ if ($this->hasMethod("afterSelect")) {
+ $afterResult = $this->afterSelect($result, "selectOne");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return Sabel_Db_Model[]
+ */
+ public function select($arg1 = null, $arg2 = null)
+ {
+ $result = null;
+ if ($this->hasMethod("beforeSelect")) {
+ $result = $this->beforeSelect("select");
+ }
+
+ if ($result === null) {
+ $this->setCondition($arg1, $arg2);
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::SELECT);
+ $rows = $this->prepareSelect($stmt)->execute();
+ $result = (empty($rows)) ? array() : $this->toModels($rows);
+ }
+
+ if ($this->hasMethod("afterSelect")) {
+ $afterResult = $this->afterSelect($result, "select");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return Sabel_Db_Model[]
+ */
+ public function selectForUpdate($arg1 = null, $arg2 = null)
+ {
+ $result = null;
+ if ($this->hasMethod("beforeSelect")) {
+ $result = $this->beforeSelect("selectForUpdate");
+ }
+
+ if ($result === null) {
+ $this->setCondition($arg1, $arg2);
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::SELECT);
+ $rows = $this->prepareSelect($stmt)->forUpdate(true)->execute();
+ $result = (empty($rows)) ? array() : $this->toModels($rows);
+ }
+
+ if ($this->hasMethod("afterSelect")) {
+ $afterResult = $this->afterSelect($result, "selectForUpdate");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param array $additionalValues
+ *
+ * @return int
+ */
+ public function save(array $additionalValues = array())
+ {
+ if ($this->isSelected()) {
+ $this->updateValues = array_merge($this->updateValues, $additionalValues);
+ $result = $this->saveUpdate();
+ } else {
+ $this->values = array_merge($this->values, $additionalValues);
+ $result = $this->saveInsert();
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @return array
+ */
+ protected function saveInsert()
+ {
+ $result = null;
+ if ($this->hasMethod("beforeInsert")) {
+ $result = $this->beforeInsert("save");
+ }
+
+ if ($result === null) {
+ $columns = $this->getColumns();
+ $saveValues = array();
+
+ foreach ($this->values as $k => $v) {
+ $saveValues[$k] = (isset($columns[$k])) ? $columns[$k]->cast($v) : $v;
+ }
+
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::INSERT);
+ $newId = $this->prepareInsert($stmt, $saveValues)->execute();
+
+ foreach ($columns as $name => $column) {
+ if (!array_key_exists($name, $saveValues)) {
+ $this->$name = $column->default;
+ }
+ }
+
+ if ($newId !== null && ($field = $this->metadata->getSequenceColumn()) !== null) {
+ $this->$field = $newId;
+ }
+
+ $result = 1;
+ }
+
+ if ($this->hasMethod("afterInsert")) {
+ $afterResult = $this->afterInsert($result, "save");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @throws Sabel_Db_Exception
+ * @return array
+ */
+ protected function saveUpdate()
+ {
+ $result = null;
+ if ($this->hasMethod("beforeUpdate")) {
+ $result = $this->beforeUpdate("save");
+ }
+
+ if ($result === null) {
+ if (($pkey = $this->metadata->getPrimaryKey()) === null) {
+ $message = __METHOD__ . "() can't update a model(there is not primary key).";
+ throw new Sabel_Db_Exception($message);
+ }
+
+ foreach ((is_string($pkey)) ? array($pkey) : $pkey as $key) {
+ if ($this->primaryKeyValues[$key] === $this->$key) {
+ $this->setCondition("{$this->modelName}.{$key}", $this->__get($key));
+ } else {
+ $message = __METHOD__ . "() can't update the primary key value.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+
+ $saveValues = array();
+ foreach ($this->updateValues as $k => $v) {
+ $saveValues[$k] = (isset($columns[$k])) ? $columns[$k]->cast($v) : $v;
+ }
+
+ $vColumn = ($this->versionColumn === "") ? "version" : $this->versionColumn;
+
+ if (isset($this->metaCols[$vColumn])) {
+ $_column = $this->metaCols[$vColumn];
+ $currentVersion = $this->__get($vColumn);
+ $this->setCondition("{$this->modelName}.{$vColumn}", $currentVersion);
+
+ if ($_column->isInt()) {
+ $saveValues[$vColumn] = $currentVersion + 1;
+ } elseif ($_column->isDatetime()) {
+ $saveValues[$vColumn] = now();
+ } else {
+ $message = __METHOD__ . "() version column must be DATETIME or INT.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+
+ $this->updateValues = array();
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::UPDATE);
+ $result = $this->prepareUpdate($stmt, $saveValues)->execute();
+ $this->values = array_merge($this->values, $saveValues);
+
+ if (isset($this->metaCols[$vColumn]) && $result < 1) {
+ $message = __METHOD__ . "() has already been changed by other transactions.";
+ throw new Sabel_Db_Exception_StaleModel($message);
+ }
+ }
+
+ if ($this->hasMethod("afterUpdate")) {
+ $afterResult = $this->afterUpdate($result, "save");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param array $values
+ *
+ * @return mixed
+ */
+ public function insert(array $values = array())
+ {
+ $result = null;
+ if ($this->hasMethod("beforeInsert")) {
+ $result = $this->beforeInsert("insert");
+ }
+
+ if ($result === null) {
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::INSERT);
+ $result = $this->prepareInsert($stmt, $values)->execute();
+ }
+
+ if ($this->hasMethod("afterInsert")) {
+ $afterResult = $this->afterInsert($result, "insert");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param array $values
+ *
+ * @return int
+ */
+ public function update(array $values = array())
+ {
+ $result = null;
+ if ($this->hasMethod("beforeUpdate")) {
+ $result = $this->beforeUpdate("update");
+ }
+
+ if ($result === null) {
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::UPDATE);
+ $result = $this->prepareUpdate($stmt, $values)->execute();
+ }
+
+ if ($this->hasMethod("afterUpdate")) {
+ $afterResult = $this->afterUpdate($result, "update");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param mixed $arg1
+ * @param mixed $arg2
+ *
+ * @return int
+ */
+ public function delete($arg1 = null, $arg2 = null)
+ {
+ $result = null;
+ if ($this->hasMethod("beforeDelete")) {
+ $result = $this->beforeDelete("delete");
+ }
+
+ if ($result === null) {
+ $condition = $this->getCondition();
+ if (!$this->isSelected() && $arg1 === null && $condition->isEmpty()) {
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::DELETE);
+ $result = $this->prepareDelete($stmt)->execute();
+ } else {
+ if ($arg1 !== null) {
+ $this->setCondition($arg1, $arg2);
+ } elseif ($this->isSelected()) {
+ if (($pkey = $this->metadata->getPrimaryKey()) === null) {
+ $message = __METHOD__ . "() cannot delete model(there is not primary key).";
+ throw new Sabel_Db_Exception($message);
+ } else {
+ foreach ((is_string($pkey)) ? array($pkey) : $pkey as $key) {
+ $this->setCondition($this->modelName . "." . $key, $this->__get($key));
+ }
+ }
+ }
+
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::DELETE);
+ $result = $this->prepareDelete($stmt)->execute();
+ }
+ }
+
+ if ($this->hasMethod("afterDelete")) {
+ $afterResult = $this->afterDelete($result, "delete");
+ if ($afterResult !== null) $result = $afterResult;
+ }
+
+ if ($this->autoReinit) $this->clear();
+
+ return $result;
+ }
+
+ /**
+ * @param const $type Sabel_Db_Statement
+ *
+ * @return Sabel_Db_Statement
+ */
+ public function prepareStatement($type = Sabel_Db_Statement::QUERY)
+ {
+ $stmt = Sabel_Db::createStatement($this->connectionName);
+ return $stmt->setMetadata($this->metadata)->type($type);
+ }
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ *
+ * @return Sabel_Db_Statement
+ */
+ public function prepareSelect(Sabel_Db_Statement $stmt)
+ {
+ return $stmt->projection($this->projection)
+ ->where($this->getCondition()->build($stmt))
+ ->constraints($this->constraints);
+ }
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ * @param array $values
+ *
+ * @return Sabel_Db_Statement
+ */
+ public function prepareUpdate(Sabel_Db_Statement $stmt, array $values = array())
+ {
+ if (empty($values)) $values = $this->values;
+ return $stmt->values($values)->where($this->getCondition()->build($stmt));
+ }
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ * @param array $values
+ *
+ * @return Sabel_Db_Statement
+ */
+ public function prepareInsert(Sabel_Db_Statement $stmt, array $values = array())
+ {
+ if (empty($values)) $values = $this->values;
+ return $stmt->values($values)->sequenceColumn($this->metadata->getSequenceColumn());
+ }
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ *
+ * @return Sabel_Db_Statement
+ */
+ public function prepareDelete(Sabel_Db_Statement $stmt)
+ {
+ return $stmt->where($this->getCondition()->build($stmt));
+ }
+
+ /**
+ * @param Sabel_Db_Model $model
+ *
+ * @return void
+ */
+ protected function _doSelectOne(Sabel_Db_Model $model)
+ {
+ $stmt = $this->prepareStatement(Sabel_Db_Statement::SELECT);
+ $rows = $this->prepareSelect($stmt)->execute();
+ if (isset($rows[0])) $model->setProperties($rows[0]);
+ }
+
+ /**
+ * @param array $rows
+ *
+ * @return Sabel_Db_Model[]
+ */
+ protected function toModels(array $rows)
+ {
+ $results = array();
+ $source = MODEL($this->modelName);
+
+ foreach ($rows as $row) {
+ $model = clone $source;
+ $model->setProperties($row);
+ $results[] = $model;
+ }
+
+ return $results;
+ }
+}
diff --git a/sabel/db/Statement.php b/sabel/db/Statement.php
new file mode 100755
index 0000000..07dea96
--- /dev/null
+++ b/sabel/db/Statement.php
@@ -0,0 +1,511 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Statement extends Sabel_Object
+{
+ const SELECT = 0x01;
+ const INSERT = 0x02;
+ const UPDATE = 0x04;
+ const DELETE = 0x08;
+ const QUERY = 0x10;
+
+ /**
+ * @var array
+ */
+ protected static $queries = array();
+
+ /**
+ * @var const Sabel_Db_Statement
+ */
+ protected $type = self::QUERY;
+
+ /**
+ * @var string
+ */
+ protected $query = "";
+
+ /**
+ * @var Sabel_Db_Driver
+ */
+ protected $driver = null;
+
+ /**
+ * @var Sabel_Db_Metadata_Table
+ */
+ protected $metadata = null;
+
+ /**
+ * @var array
+ */
+ protected $bindValues = array();
+
+ /**
+ * @var string
+ */
+ protected $table = "";
+
+ /**
+ * @var mixed
+ */
+ protected $projection = array();
+
+ /**
+ * @var string
+ */
+ protected $join = "";
+
+ /**
+ * @var string
+ */
+ protected $where = "";
+
+ /**
+ * @var array
+ */
+ protected $values = array();
+
+ /**
+ * @var array
+ */
+ protected $constraints = array();
+
+ /**
+ * @var string
+ */
+ protected $seqColumn = null;
+
+ /**
+ * @var boolean
+ */
+ protected $forUpdate = false;
+
+ /**
+ * @param array $values
+ *
+ * @return self
+ */
+ abstract public function values(array $values);
+
+ /**
+ * @param string $binaryData
+ *
+ * @return Sabel_Db_Abstract_Blob
+ */
+ abstract public function createBlob($binaryData);
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->build();
+ }
+
+ /**
+ * @return Sabel_Db_Driver
+ */
+ public function getDriver()
+ {
+ return $this->driver;
+ }
+
+ /**
+ * @return array
+ */
+ public static function getExecutedQueries()
+ {
+ return self::$queries;
+ }
+
+ /**
+ * @param Sabel_Db_Metadata_Table $metadata
+ *
+ * @return self
+ */
+ public function setMetadata(Sabel_Db_Metadata_Table $metadata)
+ {
+ $this->table = $metadata->getTableName();
+ $this->metadata = $metadata;
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ public function clear()
+ {
+ $this->query = "";
+ $this->bindValues = array();
+ $this->projection = array();
+ $this->join = "";
+ $this->where = "";
+ $this->values = array();
+ $this->constraints = array();
+ $this->seqColumn = null;
+ $this->forUpdate = false;
+
+ return $this;
+ }
+
+ public function type($type)
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ public function setQuery($query)
+ {
+ if (is_string($query)) {
+ $this->query = $query;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function getQuery()
+ {
+ return ($this->hasQuery()) ? $this->query : $this->build();
+ }
+
+ public function hasQuery()
+ {
+ return (is_string($this->query) && $this->query !== "");
+ }
+
+ public function projection($projection)
+ {
+ if (is_array($projection) || is_string($projection)) {
+ $this->projection = $projection;
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function join($join)
+ {
+ if (is_string($join)) {
+ $this->join = $join;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function where($where)
+ {
+ if (is_string($where)) {
+ $this->where = ($where === "") ? "" : " " . $where;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function constraints(array $constraints)
+ {
+ $this->constraints = $constraints;
+
+ return $this;
+ }
+
+ public function sequenceColumn($seqColumn)
+ {
+ if ($seqColumn === null) {
+ $this->seqColumn = null;
+ } elseif (is_string($seqColumn)) {
+ $this->seqColumn = $seqColumn;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function forUpdate($bool)
+ {
+ if (is_bool($bool)) {
+ $this->forUpdate = $bool;
+ } else {
+ $message = __METHOD__ . "() argument must be a boolean.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ if ($query === null) {
+ $query = $this->getQuery();
+ }
+
+ if (empty($bindValues)) {
+ $bindValues = (empty($this->bindValues)) ? array() : $this->escape($this->bindValues);
+ }
+
+ $start = microtime(true);
+ $result = $this->driver->execute($query, $bindValues, $additionalParameters);
+
+ self::$queries[] = array(
+ "sql" => $query,
+ "time" => microtime(true) - $start,
+ "binds" => $bindValues
+ );
+
+ if ($this->isInsert()) {
+ return ($this->seqColumn === null) ? null : $this->driver->getLastInsertId();
+ } elseif ($this->isUpdate() || $this->isDelete()) {
+ return $this->driver->getAffectedRows();
+ } else {
+ return $result;
+ }
+ }
+
+ public function bind($key, $val)
+ {
+ $this->bindValues["@{$key}@"] = $val;
+
+ return $this;
+ }
+
+ public function unbind($key)
+ {
+ if (is_array($key)) {
+ foreach ($key as $k) {
+ unset($this->bindValues["@{$k}@"]);
+ }
+ } else {
+ unset($this->bindValues["@{$key}@"]);
+ }
+
+ return $this;
+ }
+
+ public function binds(array $values)
+ {
+ foreach ($values as $key => $val) {
+ $this->bind($key, $val);
+ }
+
+ return $this;
+ }
+
+ public function getBinds()
+ {
+ return $this->bindValues;
+ }
+
+ public function clearBinds()
+ {
+ $this->bindValues = array();
+ }
+
+ public function isSelect()
+ {
+ return ($this->type === self::SELECT);
+ }
+
+ public function isInsert()
+ {
+ return ($this->type === self::INSERT);
+ }
+
+ public function isUpdate()
+ {
+ return ($this->type === self::UPDATE);
+ }
+
+ public function isDelete()
+ {
+ return ($this->type === self::DELETE);
+ }
+
+ public function build()
+ {
+ if ($this->type !== self::QUERY && $this->metadata === null) {
+ $message = __METHOD__ . "() can't build sql query. "
+ . "must set the metadata with setMetadata().";
+
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if ($this->isSelect()) {
+ return $this->createSelectSql();
+ } elseif ($this->isInsert()) {
+ return $this->createInsertSql();
+ } elseif ($this->isUpdate()) {
+ return $this->createUpdateSql();
+ } elseif ($this->isDelete()) {
+ return $this->createDeleteSql();
+ } else {
+ return $this->query;
+ }
+ }
+
+ protected function createSelectSql()
+ {
+ $tblName = $this->quoteIdentifier($this->table);
+ $projection = $this->getProjection();
+
+ $sql = "SELECT {$projection} FROM "
+ . $this->quoteIdentifier($this->table)
+ . $this->join
+ . $this->where
+ . $this->createConstraintSql();
+
+ if ($this->forUpdate) {
+ $sql .= " FOR UPDATE";
+ }
+
+ return $sql;
+ }
+
+ protected function createInsertSql()
+ {
+ $sql = "INSERT INTO " . $this->quoteIdentifier($this->table) . " (";
+ $cols = array();
+ $hlds = array();
+
+ foreach ($this->values as $column => $value) {
+ $cols[] = $this->quoteIdentifier($column);
+
+ if ($value instanceof Sabel_Db_Statement_Expression) {
+ unset($this->bindValues[$column]);
+ $hlds[] = $value->getExpression();
+ } else {
+ $hlds[] = "@{$column}@";
+ }
+ }
+
+ $sql .= implode(", ", $cols) . ") VALUES(" . implode(", ", $hlds) . ")";
+
+ return $sql;
+ }
+
+ protected function createUpdateSql()
+ {
+ $updates = array();
+ foreach ($this->values as $column => $value) {
+ if ($value instanceof Sabel_Db_Statement_Expression) {
+ unset($this->bindValues[$column]);
+ $updates[] = $this->quoteIdentifier($column) . " = " . $value->getExpression();
+ } else {
+ $updates[] = $this->quoteIdentifier($column) . " = @{$column}@";
+ }
+ }
+
+ $tblName = $this->quoteIdentifier($this->table);
+ return "UPDATE $tblName SET " . implode(", ", $updates) . $this->where;
+ }
+
+ protected function createDeleteSql()
+ {
+ return "DELETE FROM " . $this->quoteIdentifier($this->table) . $this->where;
+ }
+
+ protected function createConstraintSql()
+ {
+ $sql = "";
+ $c = $this->constraints;
+
+ if (isset($c["order"])) {
+ $sql .= " ORDER BY " . $this->quoteIdentifierForOrderBy($c["order"]);
+ }
+
+ if (isset($c["offset"]) && !isset($c["limit"])) {
+ $sql .= " LIMIT 100 OFFSET " . $c["offset"];
+ } else {
+ if (isset($c["limit"])) $sql .= " LIMIT " . $c["limit"];
+ if (isset($c["offset"])) $sql .= " OFFSET " . $c["offset"];
+ }
+
+ return $sql;
+ }
+
+ protected function getProjection()
+ {
+ if (empty($this->projection)) {
+ $colNames = $this->quoteIdentifier($this->metadata->getColumnNames());
+ return implode(", ", $colNames);
+ } elseif (is_string($this->projection)) {
+ return $this->projection;
+ } else {
+ $ps = array();
+ foreach ($this->projection as $p) {
+ $ps[] = $this->quoteIdentifier($p);
+ }
+
+ return implode(", ", $ps);
+ }
+ }
+
+ /**
+ * @param string $str
+ *
+ * @return string
+ */
+ public function escapeString($str)
+ {
+ $escaped = $this->escape(array($str));
+ return $escaped[0];
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '"' . $v . '"';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '"' . $arg . '"';
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ protected function quoteIdentifierForOrderBy($orders)
+ {
+ $results = array();
+ foreach ($orders as $column => $order) {
+ $mode = strtoupper($order["mode"]);
+ $nulls = strtoupper($order["nulls"]);
+
+ if (($pos = strpos($column, ".")) !== false) {
+ $tblName = convert_to_tablename(substr($column, 0, $pos));
+ $column = $this->quoteIdentifier($tblName) . "."
+ . $this->quoteIdentifier(substr($column, $pos + 1));
+ } else {
+ $column = $this->quoteIdentifier($column);
+ }
+
+ $_nulls = ($nulls === "FIRST") ? "IS NOT NULL" : "IS NULL";
+ $results[] = "{$column} {$_nulls}, {$column} {$mode}";
+ }
+
+ return implode(", ", $results);
+ }
+}
diff --git a/sabel/db/Transaction.php b/sabel/db/Transaction.php
new file mode 100755
index 0000000..c0c0708
--- /dev/null
+++ b/sabel/db/Transaction.php
@@ -0,0 +1,166 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Transaction
+{
+ const READ_UNCOMMITTED = 1;
+ const READ_COMMITTED = 2;
+ const REPEATABLE_READ = 3;
+ const SERIALIZABLE = 4;
+
+ /**
+ * @var boolean
+ */
+ private static $active = false;
+
+ /**
+ * @var resource[]
+ */
+ private static $transactions = array();
+
+ /**
+ * @var int
+ */
+ private static $isolationLevel = null;
+
+ /**
+ * @param int $isolationLevel
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return void
+ */
+ public static function activate($isolationLevel = null)
+ {
+ self::$active = true;
+ if ($isolationLevel === null) return;
+
+ if (is_numeric($isolationLevel) || $isolationLevel >= 1 && $isolationLevel <= 4) {
+ self::$isolationLevel = $isolationLevel;
+ } else {
+ $message = __METHOD__ . "() invalid isolation level.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @return boolean
+ */
+ public static function isActive($connectionName = null)
+ {
+ if ($connectionName === null) {
+ return self::$active;
+ } else {
+ return isset(self::$transactions[$connectionName]);
+ }
+ }
+
+ /**
+ * @param string $connectionName
+ *
+ * @return resource
+ */
+ public static function getConnection($connectionName)
+ {
+ if (isset(self::$transactions[$connectionName]["conn"])) {
+ return self::$transactions[$connectionName]["conn"];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param Sabel_Db_Driver $driver
+ *
+ * @throws Sabel_Db_Exception_Transaction
+ * @return void
+ */
+ public static function begin(Sabel_Db_Driver $driver)
+ {
+ switch (self::$isolationLevel) {
+ case self::READ_UNCOMMITTED:
+ $iLevel = Sabel_Db_Driver::TRANS_READ_UNCOMMITTED;
+ break;
+ case self::READ_COMMITTED:
+ $iLevel = Sabel_Db_Driver::TRANS_READ_COMMITTED;
+ break;
+ case self::REPEATABLE_READ:
+ $iLevel = Sabel_Db_Driver::TRANS_REPEATABLE_READ;
+ break;
+ case self::SERIALIZABLE:
+ $iLevel = Sabel_Db_Driver::TRANS_SERIALIZABLE;
+ break;
+ default:
+ $iLevel = null;
+ }
+
+ $connectionName = $driver->getConnectionName();
+
+ try {
+ self::$transactions[$connectionName]["conn"] = $driver->begin($iLevel);
+ self::$transactions[$connectionName]["driver"] = $driver;
+ self::$active = true;
+ } catch (Exception $e) {
+ throw new Sabel_Db_Exception_Transaction($e->getMessage());
+ }
+ }
+
+ /**
+ * @throws Sabel_Db_Exception_Transaction
+ * @return void
+ */
+ public static function commit()
+ {
+ try {
+ self::release("commit");
+ } catch (Exception $e) {
+ throw new Sabel_Db_Exception_Transaction($e->getMessage());
+ }
+ }
+
+ /**
+ * @throws Sabel_Db_Exception_Transaction
+ * @return void
+ */
+ public static function rollback()
+ {
+ try {
+ self::release("rollback");
+ } catch (Exception $e) {
+ throw new Sabel_Db_Exception_Transaction($e->getMessage());
+ }
+ }
+
+ /**
+ * @return void
+ */
+ private static function release($method)
+ {
+ if (self::$active) {
+ foreach (self::$transactions as $trans) {
+ $trans["driver"]->$method();
+ }
+
+ self::clear();
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public static function clear()
+ {
+ self::$active = false;
+ self::$transactions = array();
+ self::$isolationLevel = null;
+ }
+}
diff --git a/sabel/db/Type.php b/sabel/db/Type.php
new file mode 100755
index 0000000..af99524
--- /dev/null
+++ b/sabel/db/Type.php
@@ -0,0 +1,27 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Db_Type
+{
+ const INT = "_INT";
+ const BIGINT = "_BIGINT";
+ const SMALLINT = "_SMALLINT";
+ const FLOAT = "_FLOAT";
+ const DOUBLE = "_DOUBLE";
+ const STRING = "_STRING";
+ const TEXT = "_TEXT";
+ const BOOL = "_BOOL";
+ const DATETIME = "_DATETIME";
+ const DATE = "_DATE";
+ const BINARY = "_BINARY";
+ const UNKNOWN = "_UNKNOWN";
+}
diff --git a/sabel/db/abstract/Blob.php b/sabel/db/abstract/Blob.php
new file mode 100755
index 0000000..02db69c
--- /dev/null
+++ b/sabel/db/abstract/Blob.php
@@ -0,0 +1,28 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Abstract_Blob extends Sabel_Object
+{
+ protected $binary = "";
+
+ abstract public function getData();
+
+ public function __toString()
+ {
+ return $this->getData();
+ }
+
+ public function getRawData()
+ {
+ return $this->binary;
+ }
+}
diff --git a/sabel/db/abstract/Condition.php b/sabel/db/abstract/Condition.php
new file mode 100755
index 0000000..8635b74
--- /dev/null
+++ b/sabel/db/abstract/Condition.php
@@ -0,0 +1,133 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Abstract_Condition extends Sabel_Object
+{
+ protected static $counter = 0;
+
+ /**
+ * @var const Sabel_Db_Condition
+ */
+ protected $type;
+
+ /**
+ * @var string
+ */
+ protected $column = "";
+
+ /**
+ * @var mixed
+ */
+ protected $value = null;
+
+ /**
+ * @var boolean
+ */
+ protected $isNot = false;
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ * @param int $counter
+ *
+ * @return string
+ */
+ abstract public function build(Sabel_Db_Statement $stmt);
+
+ /**
+ * @return void
+ */
+ public static function rewind()
+ {
+ self::$counter = 0;
+ }
+
+ /**
+ * @param string $column
+ */
+ public function __construct($column)
+ {
+ if (strpos($column, ".") === false) {
+ $this->column = $column;
+ } else {
+ list($mdlName, $column) = explode(".", $column);
+ $this->column = convert_to_tablename($mdlName) . "." . $column;
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getColumn()
+ {
+ return $this->column;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param boolean $bool
+ *
+ * @return bool
+ */
+ public function isNot($bool = null)
+ {
+ if ($bool === null) {
+ return $this->isNot;
+ } elseif (is_bool($bool)) {
+ $this->isNot = $bool;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ *
+ * @return string
+ */
+ protected function getQuotedColumn($stmt)
+ {
+ if (strpos($this->column, ".") === false) {
+ return $stmt->quoteIdentifier($this->column);
+ } else {
+ list ($tbl, $col) = explode(".", $this->column);
+ return $stmt->quoteIdentifier($tbl) . "." . $stmt->quoteIdentifier($col);
+ }
+ }
+}
diff --git a/sabel/db/abstract/Metadata.php b/sabel/db/abstract/Metadata.php
new file mode 100755
index 0000000..7581844
--- /dev/null
+++ b/sabel/db/abstract/Metadata.php
@@ -0,0 +1,90 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Abstract_Metadata extends Sabel_Object
+{
+ protected $driver = null;
+ protected $schemaName = "";
+
+ abstract public function getTableList();
+ abstract public function getForeignKeys($tblName);
+ abstract public function getUniques($tblName);
+
+ public function __construct(Sabel_Db_Driver $driver, $schemaName)
+ {
+ $this->driver = $driver;
+ $this->schemaName = $schemaName;
+ }
+
+ public function getAll()
+ {
+ $tables = array();
+ foreach ($this->getTableList() as $tblName) {
+ $tables[$tblName] = $this->getTable($tblName);
+ }
+
+ return $tables;
+ }
+
+ public function getTable($tblName)
+ {
+ $columns = $this->createColumns($tblName);
+ $schema = new Sabel_Db_Metadata_Table($tblName, $columns);
+ $schema->setForeignKeys($this->getForeignKeys($tblName));
+ $schema->setUniques($this->getUniques($tblName));
+
+ return $schema;
+ }
+
+ protected function setDefaultValue($column, $default)
+ {
+ if ($default === null ||
+ (is_string($default) &&
+ ($default === "" || strtolower($default) === "null"))
+ ) {
+ $column->default = null;
+ return;
+ }
+
+ switch ($column->type) {
+ case Sabel_Db_Type::INT:
+ case Sabel_Db_Type::SMALLINT:
+ $column->default = (int)$default;
+ break;
+
+ case Sabel_Db_Type::FLOAT:
+ case Sabel_Db_Type::DOUBLE:
+ $column->default = (float)$default;
+ break;
+
+ case Sabel_Db_Type::BOOL:
+ if (is_bool($default)) {
+ $column->default = $default;
+ } else {
+ $column->default = in_array($default, array("1", "t", "true"));
+ }
+ break;
+
+ case Sabel_Db_Type::BIGINT:
+ $column->default = (string)$default;
+ break;
+
+ default:
+ $column->default = $default;
+ }
+ }
+
+ public function getTableEngine($tblName)
+ {
+ return null;
+ }
+}
diff --git a/sabel/db/abstract/Migration.php b/sabel/db/abstract/Migration.php
new file mode 100755
index 0000000..0c4bdc6
--- /dev/null
+++ b/sabel/db/abstract/Migration.php
@@ -0,0 +1,280 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Abstract_Migration extends Sabel_Object
+{
+ /**
+ * @var string
+ */
+ protected $filePath = "";
+
+ /**
+ * @var string
+ */
+ protected $tblName = "";
+
+ /**
+ * @var string
+ */
+ protected $mdlName = "";
+
+ /**
+ * @var int
+ */
+ protected $version = 0;
+
+ abstract protected function getBooleanAttr($value);
+
+ public function execute($filePath)
+ {
+ clearstatcache();
+
+ if (!is_file($filePath)) {
+ $message = __METHOD__ . "() no such file or directory.";
+ throw new Sabel_Exception_FileNotFound($message);
+ }
+
+ $this->filePath = $filePath;
+ $file = basename($filePath);
+ @list ($num, $mdlName, $command) = explode("_", $file);
+
+ if ($mdlName === "query.php") {
+ $command = "query";
+ } else {
+ $command = str_replace(".php", "", $command);
+ }
+
+ $this->version = $num;
+ $this->mdlName = $mdlName;
+ $this->tblName = convert_to_tablename($mdlName);
+
+ if ($this->hasMethod($command)) {
+ $this->$command();
+ } else {
+ $message = __METHOD__ . "() command '{$command}' not found.";
+ throw new Sabel_Db_Exception($message);
+ }
+ }
+
+ protected function create()
+ {
+ $tables = $this->getSchema()->getTableList();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (in_array($this->tblName, $tables)) {
+ Sabel_Console::warning("table '{$this->tblName}' already exists. (SKIP)");
+ } else {
+ $this->createTable($this->filePath);
+ }
+ } elseif (in_array($this->tblName, $tables, true)) {
+ $this->executeQuery("DROP TABLE " . $this->quoteIdentifier($this->tblName));
+ } else {
+ Sabel_Console::warning("unknown table '{$this->tblName}'. (SKIP)");
+ }
+ }
+
+ protected function drop()
+ {
+ $restore = $this->getRestoreFileName();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (is_file($restore)) unlink($restore);
+ $schema = $this->getSchema()->getTable($this->tblName);
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeTable($schema);
+ $this->executeQuery("DROP TABLE " . $this->quoteIdentifier($this->tblName));
+ } else {
+ $this->createTable($restore);
+ }
+ }
+
+ protected function addColumn()
+ {
+ $columns = $this->getReader()->readAddColumn()->getColumns();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ $this->execAddColumn($columns);
+ } else {
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+ foreach ($columns as $column) {
+ $colName = $this->quoteIdentifier($column->name);
+ $this->executeQuery("ALTER TABLE $quotedTblName DROP COLUMN $colName");
+ }
+ }
+ }
+
+ protected function execAddColumn($columns)
+ {
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+ $names = $this->getSchema()->getTable($this->tblName)->getColumnNames();
+
+ foreach ($columns as $column) {
+ if (in_array($column->name, $names)) {
+ Sabel_Console::warning("duplicate column '{$column->name}'. (SKIP)");
+ } else {
+ $line = $this->createColumnAttributes($column);
+ $this->executeQuery("ALTER TABLE $quotedTblName ADD $line");
+ }
+ }
+ }
+
+ protected function dropColumn()
+ {
+ $restore = $this->getRestoreFileName();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (is_file($restore)) unlink($restore);
+
+ $columns = $this->getReader()->readDropColumn()->getColumns();
+ $schema = $this->getSchema()->getTable($this->tblName);
+ $colNames = $schema->getColumnNames();
+
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeColumns($schema, $columns)->close();
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+
+ foreach ($columns as $column) {
+ if (in_array($column, $colNames)) {
+ $colName = $this->quoteIdentifier($column);
+ $this->executeQuery("ALTER TABLE $quotedTblName DROP COLUMN $colName");
+ } else {
+ Sabel_Console::warning("column '{$column}' does not exist. (SKIP)");
+ }
+ }
+ } else {
+ $columns = $this->getReader($restore)->readAddColumn()->getColumns();
+ $this->execAddColumn($columns);
+ }
+ }
+
+ protected function changeColumn()
+ {
+ $schema = $this->getSchema()->getTable($this->tblName);
+ $restore = $this->getRestoreFileName();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (is_file($restore)) unlink($restore);
+
+ $names = array();
+ $columns = $this->getReader()->readChangeColumn()->getColumns();
+ foreach ($columns as $column) $names[] = $column->name;
+
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeColumns($schema, $names, '$change')->close();
+ $this->changeColumnUpgrade($columns, $schema);
+ } else {
+ $columns = $this->getReader($restore)->readChangeColumn()->getColumns();
+ $this->changeColumnDowngrade($columns, $schema);
+ }
+ }
+
+ protected function getCreateSql($create)
+ {
+ $query = array();
+ foreach ($create->getColumns() as $column) {
+ $query[] = $this->createColumnAttributes($column);
+ }
+
+ if ($pkeys = $create->getPrimaryKeys()) {
+ $cols = $this->quoteIdentifier($pkeys);
+ $query[] = "PRIMARY KEY(" . implode(", ", $cols) . ")";
+ }
+
+ if ($fkeys = $create->getForeignKeys()) {
+ foreach ($fkeys as $fkey) {
+ $query[] = $this->createForeignKey($fkey->get());
+ }
+ }
+
+ if ($uniques = $create->getUniques()) {
+ foreach ($uniques as $unique) {
+ $cols = $this->quoteIdentifier($unique);
+ $query[] = "UNIQUE (" . implode(", ", $cols) . ")";
+ }
+ }
+
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+ return "CREATE TABLE $quotedTblName (" . implode(", ", $query) . ")";
+ }
+
+ protected function createForeignKey($object)
+ {
+ $query = "FOREIGN KEY ({$this->quoteIdentifier($object->column)}) "
+ . "REFERENCES {$this->quoteIdentifier($object->refTable)}"
+ . "({$this->quoteIdentifier($object->refColumn)})";
+
+ if ($object->onDelete !== null) {
+ $query .= " ON DELETE " . $object->onDelete;
+ }
+
+ if ($object->onUpdate !== null) {
+ $query .= " ON UPDATE " . $object->onUpdate;
+ }
+
+ return $query;
+ }
+
+ protected function getRestoreFileName()
+ {
+ $directory = Sabel_Db_Migration_Manager::getDirectory();
+ $dir = $directory . DIRECTORY_SEPARATOR . "restores";
+ if (!is_dir($dir)) mkdir($dir);
+
+ return $dir . DIRECTORY_SEPARATOR . "restore_" . $this->version . ".php";
+ }
+
+ protected function query()
+ {
+ $this->getReader()->readQuery()->execute();
+ }
+
+ protected function getReader($filePath = null)
+ {
+ if ($filePath === null) $filePath = $this->filePath;
+ return new Sabel_Db_Migration_Reader($filePath);
+ }
+
+ protected function getSchema()
+ {
+ return Sabel_Db_Migration_Manager::getMetadata();
+ }
+
+ protected function getStatement()
+ {
+ return Sabel_Db_Migration_Manager::getStatement();
+ }
+
+ protected function executeQuery($query)
+ {
+ return $this->getStatement()->setQuery($query)->execute();
+ }
+
+ protected function quoteIdentifier($arg)
+ {
+ return $this->getStatement()->quoteIdentifier($arg);
+ }
+
+ protected function getDefaultValue($column)
+ {
+ $d = $column->default;
+
+ if ($column->isBool()) {
+ return $this->getBooleanAttr($d);
+ } elseif ($d === null || $d === _NULL) {
+ return "";
+ } elseif ($column->isNumeric() && !$column->isBigint()) {
+ return "DEFAULT $d";
+ } else {
+ return "DEFAULT '{$d}'";
+ }
+ }
+}
diff --git a/sabel/db/condition/And.php b/sabel/db/condition/And.php
new file mode 100755
index 0000000..d53defe
--- /dev/null
+++ b/sabel/db/condition/And.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_And extends Sabel_Object
+{
+ protected $conditions = array();
+
+ public function add($condition)
+ {
+ $this->conditions[] = $condition;
+
+ return $this;
+ }
+
+ public function build(Sabel_Db_Statement $sql)
+ {
+ $query = array();
+ foreach ($this->conditions as $condition) {
+ $query[] = $condition->build($sql);
+ }
+
+ return "(" . implode(" AND ", $query) . ")";
+ }
+}
diff --git a/sabel/db/condition/Between.php b/sabel/db/condition/Between.php
new file mode 100755
index 0000000..acbbdb5
--- /dev/null
+++ b/sabel/db/condition/Between.php
@@ -0,0 +1,30 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_Between extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::BETWEEN;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $f = ++self::$counter;
+ $t = ++self::$counter;
+ $val = $this->value;
+
+ $stmt->bind("ph{$f}", $val[0]);
+ $stmt->bind("ph{$t}", $val[1]);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " BETWEEN @ph{$f}@ AND @ph{$t}@";
+ }
+}
diff --git a/sabel/db/condition/Direct.php b/sabel/db/condition/Direct.php
new file mode 100755
index 0000000..d42ebc2
--- /dev/null
+++ b/sabel/db/condition/Direct.php
@@ -0,0 +1,20 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_Direct extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::DIRECT;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ return $this->column;
+ }
+}
diff --git a/sabel/db/condition/Equal.php b/sabel/db/condition/Equal.php
new file mode 100755
index 0000000..f08918e
--- /dev/null
+++ b/sabel/db/condition/Equal.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_Equal extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::EQUAL;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $num = ++self::$counter;
+ $stmt->bind("ph{$num}", $this->value);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " = @ph{$num}@";
+ }
+}
diff --git a/sabel/db/condition/GreaterEqual.php b/sabel/db/condition/GreaterEqual.php
new file mode 100755
index 0000000..3ffe1ce
--- /dev/null
+++ b/sabel/db/condition/GreaterEqual.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_GreaterEqual extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::GREATER_EQUAL;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $num = ++self::$counter;
+ $stmt->bind("ph{$num}", $this->value);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " >= @ph{$num}@";
+ }
+}
diff --git a/sabel/db/condition/GreaterThan.php b/sabel/db/condition/GreaterThan.php
new file mode 100755
index 0000000..eb01209
--- /dev/null
+++ b/sabel/db/condition/GreaterThan.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_GreaterThan extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::GREATER_THAN;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $num = ++self::$counter;
+ $stmt->bind("ph{$num}", $this->value);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " > @ph{$num}@";
+ }
+}
diff --git a/sabel/db/condition/In.php b/sabel/db/condition/In.php
new file mode 100755
index 0000000..98f4c91
--- /dev/null
+++ b/sabel/db/condition/In.php
@@ -0,0 +1,30 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_In extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::IN;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ $prepared = array();
+ foreach ($this->value as $v) {
+ $n = ++self::$counter;
+ $stmt->bind("ph{$n}", $v);
+ $prepared[] = "@ph{$n}@";
+ }
+
+ return $column . " IN (" . implode(", ", $prepared) . ")";
+ }
+}
diff --git a/sabel/db/condition/IsNotNull.php b/sabel/db/condition/IsNotNull.php
new file mode 100755
index 0000000..f9aaf96
--- /dev/null
+++ b/sabel/db/condition/IsNotNull.php
@@ -0,0 +1,20 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_IsNotNull extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::ISNOTNULL;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ return $this->getQuotedColumn($stmt) . " IS NOT NULL";
+ }
+}
diff --git a/sabel/db/condition/IsNull.php b/sabel/db/condition/IsNull.php
new file mode 100755
index 0000000..306dbfd
--- /dev/null
+++ b/sabel/db/condition/IsNull.php
@@ -0,0 +1,20 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_IsNull extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::ISNULL;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ return $this->getQuotedColumn($stmt) . " IS NULL";
+ }
+}
diff --git a/sabel/db/condition/LessEqual.php b/sabel/db/condition/LessEqual.php
new file mode 100755
index 0000000..7574051
--- /dev/null
+++ b/sabel/db/condition/LessEqual.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_LessEqual extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::LESS_EQUAL;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $num = ++self::$counter;
+ $stmt->bind("ph{$num}", $this->value);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " <= @ph{$num}@";
+ }
+}
diff --git a/sabel/db/condition/LessThan.php b/sabel/db/condition/LessThan.php
new file mode 100755
index 0000000..d8d99e3
--- /dev/null
+++ b/sabel/db/condition/LessThan.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_LessThan extends Sabel_Db_Abstract_Condition
+{
+ protected $type = Sabel_Db_Condition::LESS_THAN;
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $num = ++self::$counter;
+ $stmt->bind("ph{$num}", $this->value);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " < @ph{$num}@";
+ }
+}
diff --git a/sabel/db/condition/Like.php b/sabel/db/condition/Like.php
new file mode 100755
index 0000000..0630609
--- /dev/null
+++ b/sabel/db/condition/Like.php
@@ -0,0 +1,103 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_Like extends Sabel_Db_Abstract_Condition
+{
+ const STARTS_WITH = 1;
+ const BEGINS_WITH = 1; // alias for STARTS_WITH
+ const CONTAINS = 2;
+ const ENDS_WITH = 3;
+
+ /**
+ * @var int
+ */
+ protected $type = Sabel_Db_Condition::LIKE;
+
+ /**
+ * @var int
+ */
+ private $likeType = self::BEGINS_WITH;
+
+ /**
+ * @var boolean
+ */
+ private $escape = true;
+
+ public function type($type)
+ {
+ if ($type >= 1 && $type <= 3) {
+ $this->likeType = $type;
+ } else {
+ $message = __METHOD__ . "() invalid type.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function escape($bool)
+ {
+ if (is_bool($bool)) {
+ $this->escape = $bool;
+ } else {
+ $message = __METHOD__ . "() argument must be a boolean.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ $value = $this->value;
+
+ if ($this->escape && (strpos($value, "%") !== false || strpos($value, "_") !== false)) {
+ $escapeChars = "ZQXJKVBWYGFPMUCDzqxjkvbwygfpmu";
+
+ for ($i = 0; $i < 30; $i++) {
+ $esc = $escapeChars{$i};
+ if (strpos($value, $esc) === false) {
+ $value = preg_replace("/([%_])/", $esc . '$1', $value);
+ $like = "LIKE @ph%d@ escape '{$esc}'";
+ break;
+ }
+ }
+ } else {
+ $like = "LIKE @ph%d@";
+ }
+
+ return $this->createQuery($stmt, $value, $like);
+ }
+
+ private function createQuery($stmt, $value, $part)
+ {
+ $value = $this->addSpecialCharacter($value);
+ $num = ++self::$counter;
+ $stmt->bind("ph{$num}", $value);
+
+ $column = $this->getQuotedColumn($stmt);
+ if ($this->isNot) $column = "NOT " . $column;
+
+ return $column . " " . sprintf($part, $num);
+ }
+
+ private function addSpecialCharacter($value)
+ {
+ switch ($this->likeType) {
+ case self::ENDS_WITH:
+ return "%" . $value;
+ case self::CONTAINS:
+ return "%" . $value . "%";
+ default:
+ return $value . "%";
+ }
+ }
+}
diff --git a/sabel/db/condition/Manager.php b/sabel/db/condition/Manager.php
new file mode 100755
index 0000000..f5c5e16
--- /dev/null
+++ b/sabel/db/condition/Manager.php
@@ -0,0 +1,105 @@
+
+ * @copyright 2004-2010 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_Manager extends Sabel_Object
+{
+ /**
+ * @var array
+ */
+ protected $conditions = array();
+
+ /**
+ * @param mixed $condition
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return void
+ */
+ public function add($condition)
+ {
+ if ($condition instanceof Sabel_Db_Abstract_Condition) {
+ $this->conditions[$condition->getColumn()] = $condition;
+ } elseif ($condition instanceof Sabel_Db_Condition_Or ||
+ $condition instanceof Sabel_Db_Condition_And) {
+ $this->conditions[] = $condition;
+ } else {
+ $message = "invalid condition object.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * @param string $column
+ *
+ * @return booelan
+ */
+ public function has($column)
+ {
+ return isset($this->conditions[$column]);
+ }
+
+ /**
+ * @return booelan
+ */
+ public function isEmpty()
+ {
+ return empty($this->conditions);
+ }
+
+ /**
+ * @return array
+ */
+ public function getConditions()
+ {
+ return $this->conditions;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $val
+ *
+ * @return void
+ */
+ public function create($key, $val)
+ {
+ $c = Sabel_Db_Condition::create(Sabel_Db_Condition::EQUAL, $key, $val);
+ $this->conditions[$c->getColumn()] = $c;
+ }
+
+ /**
+ * @return array
+ */
+ public function clear()
+ {
+ $conditions = $this->conditions;
+ $this->conditions = array();
+
+ return $conditions;
+ }
+
+ /**
+ * @param Sabel_Db_Statement $stmt
+ *
+ * @return string
+ */
+ public function build(Sabel_Db_Statement $stmt)
+ {
+ if (empty($this->conditions)) return "";
+
+ Sabel_Db_Abstract_Condition::rewind();
+
+ $query = array();
+ foreach ($this->conditions as $condition) {
+ $query[] = $condition->build($stmt);
+ }
+
+ return "WHERE " . implode(" AND ", $query);
+ }
+}
diff --git a/sabel/db/condition/Or.php b/sabel/db/condition/Or.php
new file mode 100755
index 0000000..2f7634c
--- /dev/null
+++ b/sabel/db/condition/Or.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Condition_Or extends Sabel_Object
+{
+ protected $conditions = array();
+
+ public function add($condition)
+ {
+ $this->conditions[] = $condition;
+
+ return $this;
+ }
+
+ public function build(Sabel_Db_Statement $sql)
+ {
+ $query = array();
+ foreach ($this->conditions as $condition) {
+ $query[] = $condition->build($sql);
+ }
+
+ return "(" . implode(" OR ", $query) . ")";
+ }
+}
diff --git a/sabel/db/exception/Connection.php b/sabel/db/exception/Connection.php
new file mode 100755
index 0000000..e99c94a
--- /dev/null
+++ b/sabel/db/exception/Connection.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Exception_Connection extends Sabel_Db_Exception
+{
+
+}
diff --git a/sabel/db/exception/Driver.php b/sabel/db/exception/Driver.php
new file mode 100755
index 0000000..5a126f7
--- /dev/null
+++ b/sabel/db/exception/Driver.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Exception_Driver extends Sabel_Db_Exception
+{
+
+}
diff --git a/sabel/db/exception/StaleModel.php b/sabel/db/exception/StaleModel.php
new file mode 100755
index 0000000..2c0f09e
--- /dev/null
+++ b/sabel/db/exception/StaleModel.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Exception_StaleModel extends Sabel_Db_Exception
+{
+
+}
diff --git a/sabel/db/exception/Transaction.php b/sabel/db/exception/Transaction.php
new file mode 100755
index 0000000..ef8225a
--- /dev/null
+++ b/sabel/db/exception/Transaction.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Exception_Transaction extends Sabel_Db_Exception
+{
+
+}
diff --git a/sabel/db/ibase/Blob.php b/sabel/db/ibase/Blob.php
new file mode 100755
index 0000000..65f6fbb
--- /dev/null
+++ b/sabel/db/ibase/Blob.php
@@ -0,0 +1,25 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Ibase_Blob extends Sabel_Db_Abstract_Blob
+{
+ public function __construct($binary)
+ {
+ $this->binary = $binary;
+ }
+
+ public function getData()
+ {
+ $blobId = ibase_blob_create();
+ ibase_blob_add($blobId, $this->binary);
+ return ibase_blob_close($blobId);
+ }
+}
diff --git a/sabel/db/ibase/Driver.php b/sabel/db/ibase/Driver.php
new file mode 100755
index 0000000..369d037
--- /dev/null
+++ b/sabel/db/ibase/Driver.php
@@ -0,0 +1,131 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Ibase_Driver extends Sabel_Db_Driver
+{
+ private $lastInsertId = null;
+ private $isolationLevel = 0;
+
+ public function connect(array $params)
+ {
+ $host = $params["host"]. ":" . $params["database"];
+ $enc = (isset($params["charset"])) ? $params["charset"] : null;
+ $conn = ibase_connect($host, $params["user"], $params["password"], $enc);
+
+ return ($conn) ? $conn : ibase_errmsg();
+ }
+
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel === null) {
+ $this->isolationLevel = IBASE_WRITE|IBASE_COMMITTED|IBASE_REC_NO_VERSION|IBASE_WAIT;
+ } else {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ $this->autoCommit = false;
+ $this->connection = ibase_trans($this->isolationLevel, $this->connection);
+ return $this->connection;
+ }
+
+ public function commit()
+ {
+ if (ibase_commit($this->connection)) {
+ $this->autoCommit = true;
+ } else {
+ throw new Sabel_Db_Exception_Driver(ibase_errmsg());
+ }
+ }
+
+ public function rollback()
+ {
+ if (ibase_rollback($this->connection)) {
+ $this->autoCommit = true;
+ } else {
+ throw new Sabel_Db_Exception_Driver(ibase_errmsg());
+ }
+ }
+
+ public function close($connection)
+ {
+ ibase_close($connection);
+ unset($this->connection);
+ }
+
+ public function setLastInsertId($id)
+ {
+ $this->lastInsertId = $id;
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $connection = $this->connection;
+
+ if (empty($bindParams)) {
+ $result = ibase_query($connection, $sql);
+ } else {
+ $holderRegex = "/@[a-zA-Z0-9_]+@/";
+ preg_match_all($holderRegex, $sql, $matches);
+ $args = array($connection, preg_replace($holderRegex, "?", $sql));
+ foreach ($matches[0] as $holder) {
+ $args[] = $bindParams[$holder];
+ }
+
+ $result = call_user_func_array("ibase_query", $args);
+ }
+
+ if (!$result) $this->executeError($sql);
+
+ $rows = array();
+ if (is_resource($result)) {
+ while ($row = ibase_fetch_assoc($result, IBASE_TEXT)) {
+ $rows[] = array_change_key_case($row);
+ }
+ ibase_free_result($result);
+ } else {
+ $this->affectedRows = ($result === true) ? 0 : $result;
+ }
+
+ if ($this->autoCommit) ibase_commit($connection);
+ return (empty($rows)) ? null : $rows;
+ }
+
+ public function getLastInsertId()
+ {
+ return $this->lastInsertId;
+ }
+
+ public function setTransactionIsolationLevel($level)
+ {
+ switch ($level) {
+ case self::TRANS_READ_UNCOMMITTED:
+ $this->isolationLevel = IBASE_WRITE|IBASE_COMMITTED|IBASE_REC_VERSION|IBASE_WAIT;
+ break;
+ case self::TRANS_READ_COMMITTED:
+ $this->isolationLevel = IBASE_WRITE|IBASE_COMMITTED|IBASE_REC_NO_VERSION|IBASE_WAIT;
+ break;
+ case self::TRANS_REPEATABLE_READ:
+ $this->isolationLevel = IBASE_WRITE|IBASE_CONCURRENCY|IBASE_WAIT;
+ break;
+ case self::TRANS_SERIALIZABLE:
+ $this->isolationLevel = IBASE_WRITE|IBASE_CONSISTENCY|IBASE_WAIT;
+ break;
+ default:
+ $message = __METHOD__ . "() invalid isolation level.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ private function executeError($sql)
+ {
+ throw new Sabel_Db_Exception_Driver(ibase_errmsg() . ", SQL: " . $sql);
+ }
+}
diff --git a/sabel/db/ibase/Metadata.php b/sabel/db/ibase/Metadata.php
new file mode 100755
index 0000000..46ed057
--- /dev/null
+++ b/sabel/db/ibase/Metadata.php
@@ -0,0 +1,221 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Ibase_Metadata extends Sabel_Db_Abstract_Metadata
+{
+ /**
+ * @var array
+ */
+ private $sequences = array();
+
+ /**
+ * @var array
+ */
+ private $primaryKeys = array();
+
+ private
+ $types = array("7" => "smallint",
+ "8" => "integer",
+ "10" => "float",
+ "12" => "date",
+ "13" => "time",
+ "14" => "char",
+ "16" => "bigint",
+ "27" => "double",
+ "35" => "timestamp",
+ "37" => "varchar",
+ "261" => "blob");
+
+ public function getTableList()
+ {
+ $sql = 'SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG = 0';
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $tables = array();
+ foreach ($rows as $row) {
+ $tables[] = trim($row['rdb$relation_name']);
+ }
+
+ return array_map("strtolower", $tables);
+ }
+
+ protected function createColumns($tblName)
+ {
+ $tblName = strtoupper($tblName);
+
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $this->createGenerators();
+ $this->createPrimaryKeys($tblName);
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $colName = strtolower(trim($row['rdb$field_name']));
+ $columns[$colName] = $this->createColumn($row, $tblName);
+ }
+
+ return $columns;
+ }
+
+ protected function createColumn($row, $tblName)
+ {
+ $fieldName = trim($row['rdb$field_name']);
+
+ $column = new Sabel_Db_Metadata_Column();
+ $column->name = strtolower($fieldName);
+ $column->nullable = ($row['rdb$null_flag'] === null);
+ $type = $this->types[$row['rdb$field_type']];
+
+ if ($type === "blob" && $row['rdb$field_sub_type'] === 1) {
+ $column->type = Sabel_Db_Type::TEXT;
+ } elseif ($type === "char" && $row['rdb$character_length'] === 1) {
+ $column->type = Sabel_Db_Type::BOOL;
+ } else {
+ Sabel_Db_Type_Manager::create()->applyType($column, $type);
+ }
+
+ $seq = "{$tblName}_{$fieldName}_SEQ";
+ $column->increment = (in_array($seq, $this->sequences));
+ $column->primary = (in_array($fieldName, $this->primaryKeys));
+
+ if (($default = $row['rdb$default_source']) !== null) {
+ $default = substr($default, 8);
+ if ($default{0} === "'") {
+ $default = substr($default, 1, -1);
+ }
+ }
+
+ $this->setDefaultValue($column, $default);
+
+ if ($column->isString()) {
+ $column->max = (int)$row['rdb$character_length'];
+ }
+
+ return $column;
+ }
+
+ public function getForeignKeys($tblName)
+ {
+ $tblName = strtoupper($tblName);
+
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $row = array_map("trim", $row);
+ $column = strtolower($row["column_name"]);
+ $columns[$column]["referenced_table"] = strtolower($row["ref_table"]);
+ $columns[$column]["referenced_column"] = strtolower($row["ref_column"]);
+ $columns[$column]["on_delete"] = $row['rdb$delete_rule'];
+ $columns[$column]["on_update"] = $row['rdb$update_rule'];
+ }
+
+ return $columns;
+ }
+
+ public function getUniques($tblName)
+ {
+ $tblName = strtoupper($tblName);
+
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $uniques = array();
+ foreach ($rows as $row) {
+ $row = array_map("trim", $row);
+ $key = $row['rdb$index_name'];
+ $uniques[$key][] = strtolower($row['rdb$field_name']);
+ }
+
+ return array_values($uniques);
+ }
+
+ private function createGenerators()
+ {
+ if (!empty($this->sequences)) return;
+
+ $sql = 'SELECT RDB$GENERATOR_NAME FROM RDB$GENERATORS '
+ . 'WHERE RDB$SYSTEM_FLAG = 0 OR RDB$SYSTEM_FLAG IS NULL';
+
+ $gens =& $this->sequences;
+ $rows = $this->driver->execute($sql);
+ if (!$rows) return;
+
+ foreach ($rows as $row) {
+ $gens[] = trim($row['rdb$generator_name']);
+ }
+ }
+
+ private function createPrimaryKeys($tblName)
+ {
+ if (!empty($this->primaryKeys)) return;
+
+ $sql = <<primaryKeys;
+ $rows = $this->driver->execute($sql);
+ if (!$rows) return;
+
+ foreach ($rows as $row) {
+ $keys[] = trim($row['rdb$field_name']);
+ }
+ }
+}
diff --git a/sabel/db/ibase/Migration.php b/sabel/db/ibase/Migration.php
new file mode 100755
index 0000000..2839d00
--- /dev/null
+++ b/sabel/db/ibase/Migration.php
@@ -0,0 +1,144 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Ibase_Migration extends Sabel_Db_Abstract_Migration
+{
+ protected $types = array(Sabel_Db_Type::INT => "INTEGER",
+ Sabel_Db_Type::BIGINT => "BIGINT",
+ Sabel_Db_Type::SMALLINT => "SMALLINT",
+ Sabel_Db_Type::FLOAT => "FLOAT",
+ Sabel_Db_Type::DOUBLE => "DOUBLE PRECISION",
+ Sabel_Db_Type::BOOL => "CHAR(1)",
+ Sabel_Db_Type::STRING => "VARCHAR",
+ Sabel_Db_Type::TEXT => "BLOB SUB_TYPE TEXT",
+ Sabel_Db_Type::DATETIME => "TIMESTAMP",
+ Sabel_Db_Type::DATE => "DATE",
+ Sabel_Db_Type::BINARY => "BLOB SUB_TYPE 2");
+
+ protected function create()
+ {
+ $tblName = convert_to_tablename($this->mdlName);
+ $schema = $this->getSchema();
+ $tables = $schema->getTableList();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (in_array($tblName, $tables)) {
+ Sabel_Console::warning("table '{$tblName}' already exists. (SKIP)");
+ } else {
+ $this->createTable($this->filePath);
+ }
+ } else {
+ if (in_array($tblName, $tables)) {
+ $this->dropSequence($schema->getTable($tblName)->getSequenceColumn());
+ $this->executeQuery("DROP TABLE " . $this->quoteIdentifier($tblName));
+ } else {
+ Sabel_Console::warning("unknown table '{$tblName}'. (SKIP)");
+ }
+ }
+ }
+
+ protected function createTable($filePath)
+ {
+ $create = $this->getReader($filePath)->readCreate();
+ $this->executeQuery($this->getCreateSql($create));
+
+ foreach ($create->getColumns() as $column) {
+ if ($column->increment) {
+ $tblName = convert_to_tablename($this->mdlName);
+ $seqName = strtoupper($tblName) . "_" . strtoupper($column->name) . "_SEQ";
+ $this->executeQuery("CREATE SEQUENCE " . $seqName);
+ }
+ }
+ }
+
+ protected function drop()
+ {
+ $restore = $this->getRestoreFileName();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (is_file($restore)) unlink($restore);
+
+ $schema = $this->getSchema()->getTable(convert_to_tablename($this->mdlName));
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeTable($schema);
+ $tblName = $this->quoteIdentifier($schema->getTableName());
+ $this->executeQuery("DROP TABLE $tblName");
+ $this->dropSequence($schema->getSequenceColumn());
+ } else {
+ $this->createTable($restore);
+ }
+ }
+
+ private function dropSequence($incCol)
+ {
+ if ($incCol !== null) {
+ $tblName = convert_to_tablename($this->mdlName);
+ $seqName = strtoupper($tblName) . "_" . strtoupper($incCol) . "_SEQ";
+ $this->executeQuery("DROP SEQUENCE " . $seqName);
+ }
+ }
+
+ protected function changeColumnUpgrade($columns, $schema)
+ {
+ throw new Sabel_Exception_Runtime("change column not supported.");
+ }
+
+ protected function changeColumnDowngrade($columns, $schema)
+ {
+ throw new Sabel_Exception_Runtime("change column not supported.");
+ }
+
+ protected function createColumnAttributes($col)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($col->name);
+ $line[] = $this->getTypeString($col);
+ $line[] = $this->getDefaultValue($col);
+
+ if (($nullable = $this->getNullableString($col)) !== "") {
+ $line[] = $nullable;
+ }
+
+ return preg_replace("/[ ]{2,}/", " ", implode(" ", $line));
+ }
+
+ private function getTypeString($col, $withLength = true)
+ {
+ if ($col->isString() && $withLength) {
+ return $this->types[$col->type] . "({$col->max})";
+ } else {
+ return $this->types[$col->type];
+ }
+ }
+
+ private function getNullableString($column)
+ {
+ return ($column->nullable === false) ? "NOT NULL" : "";
+ }
+
+ private function valueCheck($column, $default)
+ {
+ if ($default === null) return true;
+
+ if (($column->isBool() && !is_bool($default)) ||
+ ($column->isNumeric() && !is_numeric($default))) {
+ throw new Sabel_Db_Exception("invalid default value.");
+ } else {
+ return true;
+ }
+ }
+
+ protected function getBooleanAttr($value)
+ {
+ $v = ($value === true) ? "'1'" : "'0'";
+ return "DEFAULT " . $v;
+ }
+}
diff --git a/sabel/db/ibase/Statement.php b/sabel/db/ibase/Statement.php
new file mode 100755
index 0000000..0efb1b3
--- /dev/null
+++ b/sabel/db/ibase/Statement.php
@@ -0,0 +1,135 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Ibase_Statement extends Sabel_Db_Statement
+{
+ public function __construct(Sabel_Db_Ibase_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function values(array $values)
+ {
+ $columns = $this->metadata->getColumns();
+
+ foreach ($values as $k => &$v) {
+ if (isset($columns[$k]) && $columns[$k]->isBinary()) {
+ $v = $this->createBlob($v);
+ }
+ }
+
+ $this->values = $values;
+ $this->binds($values);
+
+ return $this;
+ }
+
+ public function escape(array $values)
+ {
+ foreach ($values as &$val) {
+ if (is_bool($val)) $val = ($val) ? 1 : 0;
+ }
+
+ return $values;
+ }
+
+ public function createBlob($binary)
+ {
+ return new Sabel_Db_Ibase_Blob($binary);
+ }
+
+ protected function createSelectSql()
+ {
+ $sql = "SELECT ";
+ $c = $this->constraints;
+
+ if (isset($c["limit"])) {
+ $query = "FIRST {$c["limit"]} ";
+ $query .= (isset($c["offset"])) ? "SKIP " . $c["offset"] : "SKIP 0";
+ $sql .= $query . " ";
+ } elseif (isset($c["offset"])) {
+ $sql .= "SKIP " . $c["offset"] . " ";
+ }
+
+ $projection = $this->getProjection();
+ $sql .= "$projection FROM "
+ . $this->quoteIdentifier($this->table)
+ . $this->join . $this->where
+ . $this->createConstraintSql();
+
+ if ($this->forUpdate) {
+ $sql .= " FOR UPDATE";
+ }
+
+ return $sql;
+ }
+
+ public function createInsertSql()
+ {
+ if (($column = $this->seqColumn) !== null) {
+ $seqName = strtoupper("{$this->table}_{$column}_seq");
+ $rows = $this->driver->execute("SELECT GEN_ID({$seqName}, 1) AS id FROM RDB\$DATABASE");
+ $this->values[$column] = $id = $rows[0]["id"];
+ $this->bind($column, $id);
+ $this->driver->setLastInsertId($id);
+ }
+
+ return parent::createInsertSql();
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '"' . strtoupper($v) . '"';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '"' . strtoupper($arg) . '"';
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ protected function createConstraintSql()
+ {
+ $sql = "";
+ $c = $this->constraints;
+
+ if (isset($c["order"])) {
+ $sql .= " ORDER BY " . $this->quoteIdentifierForOrderBy($c["order"]);
+ }
+
+ return $sql;
+ }
+
+ protected function quoteIdentifierForOrderBy($orders)
+ {
+ $results = array();
+ foreach ($orders as $column => $order) {
+ $mode = strtoupper($order["mode"]);
+ $nulls = strtoupper($order["nulls"]);
+
+ if (($pos = strpos($column, ".")) !== false) {
+ $tblName = convert_to_tablename(substr($column, 0, $pos));
+ $column = $this->quoteIdentifier($tblName) . "."
+ . $this->quoteIdentifier(substr($column, $pos + 1));
+ } else {
+ $column = $this->quoteIdentifier($column);
+ }
+
+ $results[] = "{$column} {$mode} NULLS {$nulls}";
+ }
+
+ return implode(", ", $results);
+ }
+}
diff --git a/sabel/db/join/Base.php b/sabel/db/join/Base.php
new file mode 100755
index 0000000..841f4dd
--- /dev/null
+++ b/sabel/db/join/Base.php
@@ -0,0 +1,150 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join_Base extends Sabel_Object
+{
+ protected $model = null;
+ protected $columns = array();
+ protected $on = array();
+ protected $joinType = "INNER";
+ protected $tblName = "";
+ protected $aliasName = "";
+ protected $childName = "";
+
+ public function __construct($model)
+ {
+ $this->model = (is_string($model)) ? MODEL($model) : $model;
+ $this->tblName = $this->model->getTableName();
+ $this->columns = $this->model->getColumnNames();
+ }
+
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ public function getName($alias = true)
+ {
+ if ($alias && $this->hasAlias()) {
+ return $this->aliasName;
+ } else {
+ return $this->tblName;
+ }
+ }
+
+ public function setAlias($alias)
+ {
+ $this->aliasName = $alias;
+
+ return $this;
+ }
+
+ public function hasAlias()
+ {
+ return !empty($this->aliasName);
+ }
+
+ public function on($on)
+ {
+ if (isset($on[0])) $on["id"] = $on[0];
+ if (isset($on[1])) $on["fkey"] = $on[1];
+
+ unset($on[0], $on[1]);
+
+ $this->on = $on;
+
+ return $this;
+ }
+
+ public function getOn()
+ {
+ return $this->on;
+ }
+
+ public function setJoinType($joinType)
+ {
+ $this->joinType = strtoupper($joinType);
+
+ return $this;
+ }
+
+ public function setChildName($name)
+ {
+ $this->childName = $name;
+
+ return $this;
+ }
+
+ public function createModel(&$row)
+ {
+ $name = $this->tblName;
+
+ static $models = array();
+
+ if (isset($models[$name])) {
+ $model = clone $models[$name];
+ } else {
+ $model = MODEL(convert_to_modelname($name));
+ $models[$name] = clone $model;
+ }
+
+ if ($this->hasAlias()) {
+ $name = strtolower($this->aliasName);
+ }
+
+ $props = array();
+ foreach ($this->columns as $column) {
+ $hash = Sabel_Db_Join_ColumnHash::getHash("{$name}.{$column}");
+ if (array_key_exists($hash, $row)) {
+ $props[$column] = $row[$hash];
+ unset($row[$hash]);
+ }
+ }
+
+ $model->setProperties($props);
+
+ return $model;
+ }
+
+ protected function _getJoinQuery(Sabel_Db_Statement $stmt)
+ {
+ $name = $stmt->quoteIdentifier($this->tblName);
+ $query = array(" {$this->joinType} JOIN {$name} ");
+
+ if ($this->hasAlias()) {
+ $name = $stmt->quoteIdentifier(strtolower($this->aliasName));
+ $query[] = "AS {$name} ";
+ }
+
+ $on = $this->on;
+ $query[] = " ON ";
+
+ if (isset($on["id"]) && isset($on["fkey"])) {
+ $query[] = $name . "." . $this->__getJoinQuery($stmt, $on);
+ } else {
+ $_on = array();
+ foreach ($on as $each) {
+ $_on[] = $name . "." . $this->__getJoinQuery($stmt, $each);
+ }
+
+ $query[] = implode(" AND ", $_on);
+ }
+
+ return $query;
+ }
+
+ private function __getJoinQuery(Sabel_Db_Statement $stmt, $on)
+ {
+ return $stmt->quoteIdentifier($on["id"])
+ . " = " . $stmt->quoteIdentifier(strtolower($this->childName))
+ . "." . $stmt->quoteIdentifier($on["fkey"]);
+ }
+}
diff --git a/sabel/db/join/ColumnHash.php b/sabel/db/join/ColumnHash.php
new file mode 100755
index 0000000..9ca4c0b
--- /dev/null
+++ b/sabel/db/join/ColumnHash.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join_ColumnHash
+{
+ private static $columns = array();
+
+ public static function toHash($as)
+ {
+ $hash = "a" . substr(md5($as), 0, 23);
+ return self::$columns[$as] = $hash;
+ }
+
+ public static function getHash($as)
+ {
+ return (isset(self::$columns[$as])) ? self::$columns[$as] : $as;
+ }
+
+ public static function clear()
+ {
+ self::$columns = array();
+ }
+}
diff --git a/sabel/db/join/Object.php b/sabel/db/join/Object.php
new file mode 100755
index 0000000..6cf7c3f
--- /dev/null
+++ b/sabel/db/join/Object.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join_Object extends Sabel_Db_Join_Base
+{
+ public function getProjection(Sabel_Db_Statement $stmt)
+ {
+ $projection = array();
+ $name = ($this->hasAlias()) ? strtolower($this->aliasName) : $this->getName(false);
+
+ foreach ($this->columns as $column) {
+ $as = "{$name}.{$column}";
+ if (strlen($as) > 30) $as = Sabel_Db_Join_ColumnHash::toHash($as);
+ $p = $stmt->quoteIdentifier($name) . "." . $stmt->quoteIdentifier($column);
+ $projection[] = $p . " AS " . $stmt->quoteIdentifier($as);
+ }
+
+ return $projection;
+ }
+
+ public function getJoinQuery(Sabel_Db_Statement $stmt)
+ {
+ return implode("", $this->_getJoinQuery($stmt));
+ }
+}
diff --git a/sabel/db/join/Relation.php b/sabel/db/join/Relation.php
new file mode 100755
index 0000000..f31bd08
--- /dev/null
+++ b/sabel/db/join/Relation.php
@@ -0,0 +1,86 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join_Relation extends Sabel_Db_Join_Base
+{
+ protected $objects = array();
+
+ public function innerJoin($object, $on = array(), $alias = "")
+ {
+ return $this->add($object, $on, $alias, "INNER");
+ }
+
+ public function leftJoin($object, $on = array(), $alias = "")
+ {
+ return $this->add($object, $on, $alias, "LEFT");
+ }
+
+ public function rightJoin($object, $on = array(), $alias = "")
+ {
+ return $this->add($object, $on, $alias, "RIGHT");
+ }
+
+ public function add($object, $on = array(), $alias = "", $type = "")
+ {
+ if (is_string($object) || is_model($object)) {
+ $object = new Sabel_Db_Join_Object($object);
+ }
+
+ if (!empty($alias)) $object->setAlias($alias);
+ if (!empty($type)) $object->setJoinType($type);
+
+ $object->setChildName($this->getName());
+
+ $structure = Sabel_Db_Join_Structure::getInstance();
+ $structure->addJoinObject($this->getName(), $object);
+ $this->objects[] = $object;
+
+ if (!empty($on)) {
+ $object->on($on);
+ } elseif ($object->getOn() === array()) {
+ $object->on(create_join_key(
+ $this->model, $object->getModel()->getTableName()
+ ));
+ }
+
+ return $this;
+ }
+
+ public function getProjection(Sabel_Db_Statement $stmt)
+ {
+ $projection = array();
+ $name = ($this->hasAlias()) ? strtolower($this->aliasName) : $this->getName(false);
+
+ foreach ($this->columns as $column) {
+ $as = "{$name}.{$column}";
+ if (strlen($as) > 30) $as = Sabel_Db_Join_ColumnHash::toHash($as);
+ $p = $stmt->quoteIdentifier($name) . "." . $stmt->quoteIdentifier($column);
+ $projection[] = $p . " AS " . $stmt->quoteIdentifier($as);
+ }
+
+ foreach ($this->objects as $object) {
+ $projection = array_merge($projection, $object->getProjection($stmt));
+ }
+
+ return $projection;
+ }
+
+ public function getJoinQuery(Sabel_Db_Statement $stmt)
+ {
+ $query = $this->_getJoinQuery($stmt);
+
+ foreach ($this->objects as $object) {
+ $query[] = $object->getJoinQuery($stmt);
+ }
+
+ return implode("", $query);
+ }
+}
diff --git a/sabel/db/join/Result.php b/sabel/db/join/Result.php
new file mode 100755
index 0000000..9ca5c74
--- /dev/null
+++ b/sabel/db/join/Result.php
@@ -0,0 +1,64 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join_Result
+{
+ public static function build(Sabel_Db_Model $source, Sabel_Db_Join_Structure $structure, $rows)
+ {
+ $objects = $structure->getJoinObjects();
+ $structure = $structure->getStructure();
+
+ $tables = array();
+ foreach ($structure as $joinTables) {
+ $tables = array_merge($tables, $joinTables);
+ }
+
+ $results = array();
+ $selfObj = MODEL($source->getName());
+
+ foreach ($rows as $row) {
+ $models = self::createModels($row, $tables, $objects);
+
+ foreach ($tables as $tblName) {
+ if (!isset($structure[$tblName])) continue;
+ foreach ($structure[$tblName] as $parent) {
+ $name = convert_to_modelname($parent);
+ $models[$tblName]->__set($name, $models[$parent]);
+ }
+ }
+
+ $self = clone $selfObj;
+ $self->setProperties($row);
+
+ $tblName = $source->getTableName();
+ foreach ($structure[$tblName] as $parent) {
+ $name = convert_to_modelname($parent);
+ $self->__set($name, $models[$parent]);
+ }
+
+ $results[] = $self;
+ }
+
+ return $results;
+ }
+
+ private static function createModels(&$row, $tables, $objects)
+ {
+ $models = array();
+ foreach ($tables as $tblName) {
+ $object = $objects[$tblName];
+ $model = $object->createModel($row);
+ $models[$tblName] = $model;
+ }
+
+ return $models;
+ }
+}
diff --git a/sabel/db/join/Structure.php b/sabel/db/join/Structure.php
new file mode 100755
index 0000000..91bde47
--- /dev/null
+++ b/sabel/db/join/Structure.php
@@ -0,0 +1,76 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Join_Structure
+{
+ private static $instance = null;
+
+ private $structure = array();
+ private $joinObjects = array();
+
+ private function __construct() {}
+
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function addJoinObject($child, $parentObj)
+ {
+ $parent = $parentObj->getName();
+ $this->joinObjects[$parent] = $parentObj;
+ $this->structure[$child][] = $parent;
+ }
+
+ public function getStructure($unset = true)
+ {
+ if ($unset) {
+ $structure = $this->structure;
+ $this->structure = array();
+ return $structure;
+ } else {
+ return $this->structure;
+ }
+ }
+
+ public function getJoinObjects($unset = true)
+ {
+ if ($unset) {
+ $joinObjects = $this->joinObjects;
+ $this->joinObjects = array();
+ return $joinObjects;
+ } else {
+ return $this->joinObjects;
+ }
+ }
+
+ public function setAlias($source, $alias)
+ {
+ $structure =& $this->structure;
+
+ if (isset($structure[$source])) {
+ $structure[$alias] = $structure[$source];
+ unset($structure[$source]);
+ }
+ }
+
+ public function clear()
+ {
+ $this->structure = array();
+ $this->joinObjects = array();
+
+ self::$instance = null;
+ }
+}
diff --git a/sabel/db/metadata/Column.php b/sabel/db/metadata/Column.php
new file mode 100755
index 0000000..9f01de2
--- /dev/null
+++ b/sabel/db/metadata/Column.php
@@ -0,0 +1,232 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Metadata_Column extends Sabel_Object
+{
+ public $name = null;
+ public $type = null;
+ public $nullable = null;
+ public $default = null;
+ public $primary = null;
+ public $increment = null;
+ public $max = null;
+ public $min = null;
+ public $value = null;
+
+ public static function create($name, array $props)
+ {
+ if (!isset($props["type"])) {
+ $message = __METHOD__ . "() must set column 'type'.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $self = new self();
+ $self->name = $name;
+ $self->type = $props["type"];
+
+ if ($self->isNumeric()) {
+ switch ($self->type) {
+ case Sabel_Db_Type::INT:
+ $self->max = (isset($props["max"])) ? $props["max"] : PHP_INT_MAX;
+ $self->min = (isset($props["min"])) ? $props["min"] : -PHP_INT_MAX - 1;
+ break;
+ case Sabel_Db_Type::SMALLINT:
+ $self->max = (isset($props["max"])) ? $props["max"] : 32767;
+ $self->min = (isset($props["min"])) ? $props["min"] : -32768;
+ break;
+ case Sabel_Db_Type::BIGINT:
+ $self->max = (isset($props["max"])) ? $props["max"] : 9223372036854775807;
+ $self->min = (isset($props["min"])) ? $props["min"] : -9223372036854775808;
+ break;
+ case Sabel_Db_Type::FLOAT:
+ $self->max = (isset($props["max"])) ? $props["max"] : 3.4028235E+38;
+ $self->min = (isset($props["min"])) ? $props["min"] : -3.4028235E+38;
+ break;
+ case Sabel_Db_Type::DOUBLE:
+ $self->max = (isset($props["max"])) ? $props["max"] : 1.79769E+308;
+ $self->min = (isset($props["min"])) ? $props["min"] : -1.79769E+308;
+ break;
+ }
+ } elseif ($self->isString()) {
+ $self->max = (isset($props["max"])) ? $props["max"] : 255;
+ $self->min = (isset($props["min"])) ? $props["min"] : 0;
+ }
+
+ $self->increment = (isset($props["increment"]) && $props["increment"]);
+ $self->primary = (isset($props["primary"]) && $props["primary"]);
+ $self->nullable = (isset($props["nullable"]) && $props["nullable"]);
+ $self->default = (isset($props["default"])) ? $props["default"] : null;
+
+ return $self;
+ }
+
+ public function isInt($strict = false)
+ {
+ if ($strict) {
+ return ($this->type === Sabel_Db_Type::INT);
+ } else {
+ return (
+ $this->type === Sabel_Db_Type::INT ||
+ $this->type === Sabel_Db_Type::BIGINT ||
+ $this->type === Sabel_Db_Type::SMALLINT
+ );
+ }
+ }
+
+ public function isBigint()
+ {
+ return ($this->type === Sabel_Db_Type::BIGINT);
+ }
+
+ public function isSmallint()
+ {
+ return ($this->type === Sabel_Db_Type::SMALLINT);
+ }
+
+ public function isFloat($strict = false)
+ {
+ if ($strict) {
+ return ($this->type === Sabel_Db_Type::FLOAT);
+ } else {
+ return (
+ $this->type === Sabel_Db_Type::FLOAT ||
+ $this->type === Sabel_Db_Type::DOUBLE
+ );
+ }
+ }
+
+ public function isDouble()
+ {
+ return ($this->type === Sabel_Db_Type::DOUBLE);
+ }
+
+ public function isString()
+ {
+ return ($this->type === Sabel_Db_Type::STRING);
+ }
+
+ public function isText()
+ {
+ return ($this->type === Sabel_Db_Type::TEXT);
+ }
+
+ public function isDatetime()
+ {
+ return ($this->type === Sabel_Db_Type::DATETIME);
+ }
+
+ public function isDate()
+ {
+ return ($this->type === Sabel_Db_Type::DATE);
+ }
+
+ public function isBoolean()
+ {
+ return ($this->type === Sabel_Db_Type::BOOL);
+ }
+
+ public function isBool()
+ {
+ return $this->isBoolean();
+ }
+
+ public function isBinary()
+ {
+ return ($this->type === Sabel_Db_Type::BINARY);
+ }
+
+ public function isNumeric()
+ {
+ return ($this->isInt() || $this->isFloat() || $this->isDouble());
+ }
+
+ public function isUnknown($strict = false)
+ {
+ if ($strict) {
+ return ($this->type === Sabel_Db_Type::UNKNOWN);
+ } else {
+ return ($this->type === Sabel_Db_Type::UNKNOWN || $this->type === null);
+ }
+ }
+
+ public function cast($value)
+ {
+ if ($value === null) return null;
+
+ switch ($this->type) {
+ case Sabel_Db_Type::INT:
+ return (is_int($value)) ? $value : $this->toInteger($value, PHP_INT_MAX, -PHP_INT_MAX - 1);
+
+ case Sabel_Db_Type::STRING:
+ case Sabel_Db_Type::TEXT:
+ case Sabel_Db_Type::BIGINT:
+ return (is_int($value) || is_float($value)) ? (string)$value : $value;
+
+ case Sabel_Db_Type::BOOL:
+ if (is_string($value)) {
+ if ($value === "1" || $value === "t" || $value === "true") {
+ return true;
+ } elseif ($value === "0" || $value === "f" || $value === "false") {
+ return false;
+ }
+ } elseif (is_int($value)) {
+ if ($value === 1) {
+ return true;
+ } elseif ($value === 0) {
+ return false;
+ }
+ }
+ return $value;
+
+ case Sabel_Db_Type::DATETIME:
+ $result = strtotime($value);
+ return ($result === false) ? $value : date("Y-m-d H:i:s", $result);
+
+ case Sabel_Db_Type::SMALLINT:
+ return $this->toInteger($value, 32767, -32768);
+
+ case Sabel_Db_Type::DATE:
+ $result = strtotime($value);
+ return ($result === false) ? $value : date("Y-m-d", $result);
+
+ case Sabel_Db_Type::FLOAT:
+ case Sabel_Db_Type::DOUBLE:
+ if ((is_string($value) && $value === (string)(float)$value) || is_int($value)) {
+ return (float)$value;
+ } else {
+ return $value;
+ }
+
+ default:
+ return $value;
+ }
+ }
+
+ public function setValue($value)
+ {
+ $this->value = $this->cast($value);
+ }
+
+ private function toInteger($value, $max, $min)
+ {
+ if (is_string($value)) {
+ if (preg_match('/^[+|-]?[0-9]+(\.?[0-9]+)?$/', $value) === 0) {
+ return $value;
+ } else {
+ return ($value >= $min && $value <= $max) ? (int)$value : $value;
+ }
+ } elseif (is_float($value) && fmod($value, 1.0) === 0.0 && $value <= $max) {
+ return (int)$value;
+ } else {
+ return $value;
+ }
+ }
+}
diff --git a/sabel/db/metadata/FileWriter.php b/sabel/db/metadata/FileWriter.php
new file mode 100755
index 0000000..c104151
--- /dev/null
+++ b/sabel/db/metadata/FileWriter.php
@@ -0,0 +1,169 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Metadata_FileWriter extends Sabel_Object
+{
+ private $schemaDir = "";
+
+ public function __construct($schemaDir)
+ {
+ if (is_dir($schemaDir)) {
+ $this->schemaDir = $schemaDir;
+ } else {
+ throw new Sabel_Db_Exception("no such directory: '{$schemaDir}'");
+ }
+ }
+
+ public function write(Sabel_Db_Metadata_Table $metadata)
+ {
+ $mdlName = convert_to_modelname($metadata->getTableName());
+ $className = "Schema_" . $mdlName;
+ $target = $this->schemaDir . DS . $mdlName . ".php";
+
+ if (file_exists($target)) unlink($target);
+
+ $lines = array();
+ $lines[] = "createColumnLines($metadata);
+ foreach ($colLines as $line) {
+ $lines[] = " " . $line;
+ }
+
+ $lines[] = PHP_EOL;
+ $lines[] = ' return $cols;';
+ $lines[] = " }" . PHP_EOL;
+
+ $lines[] = " public function getProperty()";
+ $lines[] = " {";
+ $lines[] = ' $property = array();' . PHP_EOL;
+
+ $this->writeEngine($lines, $metadata);
+ $this->writeUniques($lines, $metadata);
+ $this->writeForeignKeys($lines, $metadata);
+
+ $lines[] = PHP_EOL;
+ $lines[] = " return \$property;";
+ $lines[] = " }";
+ $lines[] = "}";
+
+ $fp = fopen($target, "w");
+ fwrite($fp, implode(PHP_EOL, $lines));
+ fclose($fp);
+ }
+
+ private function createColumnLines($metadata)
+ {
+ $lines = array();
+ $columns = $metadata->getColumns();
+
+ foreach ($columns as $col) {
+ $line = array();
+ $isNum = false;
+
+ $line[] = '$cols[' . "'{$col->name}'] = array(";
+
+ $type = str_replace("_", "", $col->type);
+ $line[] = "'type' => Sabel_Db_Type::{$type}, ";
+
+ if ($col->isInt() || $col->isFloat() || $col->isDouble()) {
+ $line[] = "'max' => {$col->max}, ";
+ $line[] = "'min' => {$col->min}, ";
+ $isNum = true;
+ } elseif ($col->isString()) {
+ if ($col->min === null) $col->min = 0;
+ $line[] = "'min' => {$col->min}, ";
+ $line[] = "'max' => {$col->max}, ";
+ }
+
+ $this->setConstraints($line, $col);
+
+ $line[] = "'default' => " . $this->getDefault($isNum, $col);
+ $lines[$col->name] = implode("", $line) . ");";
+ }
+
+ return $lines;
+ }
+
+ private function setConstraints(&$line, $column)
+ {
+ $increment = ($column->increment) ? "true" : "false";
+ $nullable = ($column->nullable) ? "true" : "false";
+ $primary = ($column->primary) ? "true" : "false";
+
+ $line[] = "'increment' => {$increment}, ";
+ $line[] = "'nullable' => {$nullable}, ";
+ $line[] = "'primary' => {$primary}, ";
+ }
+
+ private function getDefault($isNum, $column)
+ {
+ $default = $column->default;
+
+ if ($default === null) {
+ $str = "null";
+ } elseif ($isNum) {
+ $str = $default;
+ } elseif ($column->isBool()) {
+ $str = ($default) ? "true" : "false";
+ } else {
+ $str = "'" . $default . "'";
+ }
+
+ return $str;
+ }
+
+ private function writeEngine(&$lines, $metadata)
+ {
+ $engine = $metadata->getTableEngine();
+ $lines[] = " \$property['tableEngine'] = '{$engine}';";
+ }
+
+ private function writeUniques(&$lines, $metadata)
+ {
+ $uniques = $metadata->getUniques();
+
+ if ($uniques === null) {
+ $lines[] = " \$property['uniques'] = null;";
+ } else {
+ foreach ($uniques as $unique) {
+ $us = array();
+ foreach ($unique as $u) $us[] = "'" . $u . "'";
+ $us = implode(", ", $us);
+ $lines[] = " \$property['uniques'][] = array({$us});";
+ }
+ }
+ }
+
+ private function writeForeignKeys(&$lines, $metadata)
+ {
+ $fkey = $metadata->getForeignKey();
+
+ if ($fkey === null) {
+ $lines[] = " \$property['fkeys'] = null;";
+ } else {
+ $space = " ";
+ foreach ($fkey->toArray() as $column => $params) {
+ $lines[] = " \$property['fkeys']['{$column}'] = "
+ . "array('referenced_table' => '{$params->table}',";
+
+ $lines[] = $space . "'referenced_column' => '{$params->column}',";
+ $lines[] = $space . "'on_delete' => '{$params->onDelete}',";
+ $lines[] = $space . "'on_update' => '{$params->onUpdate}');";
+ }
+ }
+ }
+}
diff --git a/sabel/db/metadata/ForeignKey.php b/sabel/db/metadata/ForeignKey.php
new file mode 100755
index 0000000..e8c518d
--- /dev/null
+++ b/sabel/db/metadata/ForeignKey.php
@@ -0,0 +1,68 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Metadata_ForeignKey extends Sabel_Object
+{
+ /**
+ * @var array
+ */
+ private $fkeys = array();
+
+ /**
+ * @var array
+ */
+ private $objects = array();
+
+ public function __construct($fkeys)
+ {
+ if (is_array($fkeys)) {
+ $this->fkeys = $fkeys;
+ } else {
+ $message = __METHOD__ . "() argument must be an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ public function has($key)
+ {
+ return isset($this->fkeys[$key]);
+ }
+
+ public function __get($key)
+ {
+ if ($this->has($key)) {
+ if (isset($this->objects[$key])) {
+ return $this->objects[$key];
+ } else {
+ $fkey = $this->fkeys[$key];
+ $stdClass = new stdClass();
+ $stdClass->table = $fkey["referenced_table"];
+ $stdClass->column = $fkey["referenced_column"];
+ $stdClass->onDelete = $fkey["on_delete"];
+ $stdClass->onUpdate = $fkey["on_update"];
+
+ return $this->objects[$key] = $stdClass;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public function toArray()
+ {
+ $fkeys = array();
+ foreach (array_keys($this->fkeys) as $key) {
+ $fkeys[$key] = $this->__get($key);
+ }
+
+ return $fkeys;
+ }
+}
diff --git a/sabel/db/metadata/Table.php b/sabel/db/metadata/Table.php
new file mode 100755
index 0000000..2c1b3c0
--- /dev/null
+++ b/sabel/db/metadata/Table.php
@@ -0,0 +1,160 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Metadata_Table extends Sabel_Object
+{
+ protected $tableName = "";
+ protected $columns = array();
+ protected $primaryKey = null;
+ protected $foreignKeys = null;
+ protected $uniques = null;
+ protected $incrementColumn = null;
+ protected $tableEngine = null;
+
+ public function __construct($name, $columns)
+ {
+ $this->tableName = $name;
+ $this->columns = $columns;
+
+ $this->setPrimaryKey();
+ $this->setSequenceColumn();
+ }
+
+ public function setColumn(Sabel_Db_Metadata_Column $column)
+ {
+ $this->columns[$column->name] = $column;
+ }
+
+ public function __get($key)
+ {
+ return $this->getColumnByName($key);
+ }
+
+ public function getTableName()
+ {
+ return $this->tableName;
+ }
+
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ public function getColumnByName($name)
+ {
+ return (isset($this->columns[$name])) ? $this->columns[$name] : null;
+ }
+
+ public function getColumnNames()
+ {
+ return array_keys($this->columns);
+ }
+
+ public function hasColumn($name)
+ {
+ return isset($this->columns[$name]);
+ }
+
+ public function getPrimaryKey()
+ {
+ return $this->primaryKey;
+ }
+
+ public function getSequenceColumn()
+ {
+ return $this->incrementColumn;
+ }
+
+ public function setForeignKeys($fkeys)
+ {
+ if ($fkeys === null) return;
+ $this->foreignKey = new Sabel_Db_Metadata_ForeignKey($fkeys);
+ }
+
+ public function getForeignKey()
+ {
+ return $this->foreignKey;
+ }
+
+ public function isForeignKey($colName)
+ {
+ return isset($this->foreignKeys[$colName]);
+ }
+
+ public function setUniques($uniques)
+ {
+ $this->uniques = $uniques;
+ }
+
+ public function getUniques()
+ {
+ return $this->uniques;
+ }
+
+ public function isUnique($colName)
+ {
+ $uniques = $this->uniques;
+ if ($uniques === null) return false;
+
+ foreach ($uniques as $unique) {
+ if (in_array($colName, $unique)) return true;
+ }
+
+ return false;
+ }
+
+ public function setTableEngine($engine)
+ {
+ $this->tableEngine = $engine;
+ }
+
+ public function getTableEngine()
+ {
+ return $this->tableEngine;
+ }
+
+ private function setPrimaryKey()
+ {
+ $pkey = array();
+ foreach ($this->columns as $column) {
+ if ($column->primary) $pkey[] = $column->name;
+ }
+
+ switch (count($pkey)) {
+ case 0:
+ $this->primaryKey = null;
+ break;
+
+ case 1:
+ $this->primaryKey = $pkey[0];
+ break;
+
+ default:
+ $this->primaryKey = $pkey;
+ break;
+
+ }
+ }
+
+ private function setSequenceColumn()
+ {
+ $incrementColumn = null;
+
+ foreach ($this->columns as $column) {
+ if ($column->increment) {
+ $incrementColumn = $column->name;
+ break;
+ }
+ }
+
+ $this->incrementColumn = $incrementColumn;
+ }
+}
diff --git a/sabel/db/migration/AddColumn.php b/sabel/db/migration/AddColumn.php
new file mode 100755
index 0000000..bd4d1d8
--- /dev/null
+++ b/sabel/db/migration/AddColumn.php
@@ -0,0 +1,41 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_AddColumn
+{
+ private
+ $mcolumns = array(),
+ $columns = array();
+
+ public function column($name)
+ {
+ $column = new Sabel_Db_Migration_Column($name);
+ return $this->mcolumns[$name] = $column;
+ }
+
+ public function build()
+ {
+ $columns = array();
+
+ foreach ($this->mcolumns as $column) {
+ $columns[] = $column->arrange()->getColumn();
+ }
+
+ $this->columns = $columns;
+
+ return $this;
+ }
+
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+}
diff --git a/sabel/db/migration/ChangeColumn.php b/sabel/db/migration/ChangeColumn.php
new file mode 100755
index 0000000..1c89469
--- /dev/null
+++ b/sabel/db/migration/ChangeColumn.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_ChangeColumn
+{
+ private
+ $mcolumns = array(),
+ $columns = array();
+
+ public function column($name)
+ {
+ $column = new Sabel_Db_Migration_Column($name, true);
+ return $this->mcolumns[$name] = $column;
+ }
+
+ public function getColumns()
+ {
+ $columns = array();
+ foreach ($this->mcolumns as $column) {
+ $columns[] = $column->getColumn();
+ }
+
+ return $columns;
+ }
+}
diff --git a/sabel/db/migration/Column.php b/sabel/db/migration/Column.php
new file mode 100755
index 0000000..dd319c3
--- /dev/null
+++ b/sabel/db/migration/Column.php
@@ -0,0 +1,133 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_Column
+{
+ /**
+ * @var Sabel_Db_Metadata_Column
+ */
+ private $column = null;
+
+ /**
+ * @var boolean
+ */
+ private $isChange = false;
+
+ public function __construct($name, $isChange = false)
+ {
+ $this->column = new Sabel_Db_Metadata_Column();
+ $this->column->name = $name;
+ $this->isChange = $isChange;
+ }
+
+ public function getColumn()
+ {
+ return $this->column;
+ }
+
+ public function type($type)
+ {
+ $const = @constant($type);
+
+ if ($const === null) {
+ Sabel_Console::error("datatype '{$type}' is not supported.");
+ exit;
+ } else {
+ $this->column->type = $const;
+ }
+
+ return $this;
+ }
+
+ public function primary($bool)
+ {
+ $this->setBoolean($bool, "primary");
+ return $this;
+ }
+
+ public function increment($bool)
+ {
+ $this->setBoolean($bool, "increment");
+ return $this;
+ }
+
+ public function nullable($bool)
+ {
+ $this->setBoolean($bool, "nullable");
+ return $this;
+ }
+
+ public function value($value)
+ {
+ if ($this->column->isBool() && !is_bool($value)) {
+ Sabel_Console::error("default value for BOOL column should be a boolean.");
+ exit;
+ } else {
+ $this->column->default = $value;
+ return $this;
+ }
+ }
+
+ public function length($length)
+ {
+ if ($this->column->isString() || $this->isChange && $this->column->type === null) {
+ $this->column->max = $length;
+ return $this;
+ } else {
+ Sabel_Console::error("length() for _STRING column.");
+ exit;
+ }
+ }
+
+ private function setBoolean($bool, $key)
+ {
+ if (is_bool($bool)) {
+ $this->column->$key = $bool;
+ } else {
+ Sabel_Console::error("argument for {$key}() should be a boolean.");
+ exit;
+ }
+ }
+
+ public function arrange()
+ {
+ $column = $this->column;
+
+ if ($column->primary === true) {
+ $column->nullable = false;
+ } elseif ($column->nullable === null) {
+ $column->nullable = true;
+ }
+
+ if ($column->primary === null) {
+ $column->primary = false;
+ }
+
+ if ($column->increment === null) {
+ $column->increment = false;
+ }
+
+ if ($column->type === Sabel_Db_Type::STRING &&
+ $column->max === null) $column->max = 255;
+
+ if ($column->type === Sabel_Db_Type::INT) {
+ if ($column->max === null) $column->max = PHP_INT_MAX;
+ if ($column->min === null) $column->min = -PHP_INT_MAX - 1;
+ }
+
+ if ($column->type === Sabel_Db_Type::SMALLINT) {
+ if ($column->max === null) $column->max = 32767;
+ if ($column->min === null) $column->min = -32768;
+ }
+
+ return $this;
+ }
+}
diff --git a/sabel/db/migration/Create.php b/sabel/db/migration/Create.php
new file mode 100755
index 0000000..8153aa4
--- /dev/null
+++ b/sabel/db/migration/Create.php
@@ -0,0 +1,116 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_Create
+{
+ private $mcolumns = array();
+ private $columns = array();
+ private $pkeys = array();
+ private $fkeys = array();
+ private $uniques = array();
+ private $indexes = array();
+ private $options = array();
+
+ public function column($name)
+ {
+ $mcolumn = new Sabel_Db_Migration_Column($name);
+
+ return $this->mcolumns[$name] = $mcolumn;
+ }
+
+ public function build()
+ {
+ $columns = array();
+
+ foreach ($this->mcolumns as $column) {
+ $columns[] = $column->arrange()->getColumn();
+ }
+
+ $pkeys =& $this->pkeys;
+ foreach ($columns as $column) {
+ if ($column->primary) $pkeys[] = $column->name;
+ }
+
+ $this->columns = $columns;
+
+ return $this;
+ }
+
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ public function getPrimaryKeys()
+ {
+ return $this->pkeys;
+ }
+
+ public function getForeignKeys()
+ {
+ return $this->fkeys;
+ }
+
+ public function getUniques()
+ {
+ return $this->uniques;
+ }
+
+ public function getIndexes()
+ {
+ return $this->indexes;
+ }
+
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ public function primary($columnNames)
+ {
+ if (is_string($columnNames)) {
+ $this->pkeys = array($columnNames);
+ } elseif (is_array($columnNames)) {
+ $this->pkeys = $columnNames;
+ } else {
+ Sabel_Console::error("primary() argument must be a string or an array.");
+ exit;
+ }
+ }
+
+ public function unique($columnNames)
+ {
+ if (is_string($columnNames)) {
+ $this->uniques[] = (array)$columnNames;
+ } elseif (is_array($columnNames)) {
+ $this->uniques[] = $columnNames;
+ } else {
+ Sabel_Console::error("unique() argument should be a string or an array.");
+ exit;
+ }
+ }
+
+ public function fkey($colName)
+ {
+ $fKey = new Sabel_Db_Migration_ForeignKey($colName);
+ return $this->fkeys[$colName] = $fKey;
+ }
+
+ public function index($colName)
+ {
+ $this->indexes[] = $colName;
+ }
+
+ public function options($key, $val)
+ {
+ $this->options[$key] = $val;
+ }
+}
diff --git a/sabel/db/migration/DropColumn.php b/sabel/db/migration/DropColumn.php
new file mode 100755
index 0000000..c1b9ec3
--- /dev/null
+++ b/sabel/db/migration/DropColumn.php
@@ -0,0 +1,25 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_DropColumn
+{
+ private $columns = array();
+
+ public function column($name)
+ {
+ $this->columns[] = $name;
+ }
+
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+}
diff --git a/sabel/db/migration/ForeignKey.php b/sabel/db/migration/ForeignKey.php
new file mode 100755
index 0000000..ede811a
--- /dev/null
+++ b/sabel/db/migration/ForeignKey.php
@@ -0,0 +1,71 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_ForeignKey
+{
+ public
+ $column = null,
+ $refTable = null,
+ $refColumn = null,
+ $onDelete = null,
+ $onUpdate = null;
+
+ public function __construct($column)
+ {
+ $this->column = $column;
+ }
+
+ public function get()
+ {
+ if ($this->refTable === null && $this->refColumn === null) {
+ $table = substr($this->column, 0, -3);
+ $column = str_replace($table, "", $this->column);
+ if ($column === "_id") {
+ $column = "id";
+ } else {
+ throw new Sabel_Db_Exception("invalid column name for foreign key.");
+ }
+
+ $this->refTable = $table;
+ $this->refColumn = $column;
+ }
+
+ return $this;
+ }
+
+ public function table($tblName)
+ {
+ $this->refTable = $tblName;
+
+ return $this;
+ }
+
+ public function column($colName)
+ {
+ $this->refColumn = $colName;
+
+ return $this;
+ }
+
+ public function onDelete($arg)
+ {
+ $this->onDelete = $arg;
+
+ return $this;
+ }
+
+ public function onUpdate($arg)
+ {
+ $this->onUpdate = $arg;
+
+ return $this;
+ }
+}
diff --git a/sabel/db/migration/Manager.php b/sabel/db/migration/Manager.php
new file mode 100755
index 0000000..25470cc
--- /dev/null
+++ b/sabel/db/migration/Manager.php
@@ -0,0 +1,102 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_Manager
+{
+ private static $metadata = null;
+ private static $stmt = null;
+ private static $directory = "";
+ private static $applyMode = "";
+
+ public static function setMetadata(Sabel_Db_Abstract_Metadata $metadata)
+ {
+ self::$metadata = $metadata;
+ }
+
+ public static function getMetadata()
+ {
+ return self::$metadata;
+ }
+
+ public static function setStatement(Sabel_Db_Statement $stmt)
+ {
+ self::$stmt = $stmt;
+ }
+
+ public static function getStatement()
+ {
+ self::$stmt->clear();
+
+ return self::$stmt;
+ }
+
+ public static function setApplyMode($type)
+ {
+ self::$applyMode = $type;
+ }
+
+ public static function isUpgrade()
+ {
+ return (self::$applyMode === "upgrade");
+ }
+
+ public static function isDowngrade()
+ {
+ return (self::$applyMode === "downgrade");
+ }
+
+ public static function setDirectory($directory)
+ {
+ self::$directory = $directory;
+ }
+
+ public static function getDirectory()
+ {
+ return self::$directory;
+ }
+
+ public static function getFiles()
+ {
+ if (!is_dir(self::$directory)) {
+ Sabel_Console::error("no such dirctory. '" . self::$directory . "'");
+ exit;
+ }
+
+ $files = array();
+ foreach (scandir(self::$directory) as $file) {
+ $num = substr($file, 0, strpos($file, "_"));
+ if (!is_numeric($num)) continue;
+
+ if (isset($files[$num])) {
+ Sabel_Console::error("the same version({$num}) files exists.");
+ exit;
+ } else {
+ $files[$num] = $file;
+ }
+ }
+
+ ksort($files);
+ return $files;
+ }
+}
diff --git a/sabel/db/migration/Query.php b/sabel/db/migration/Query.php
new file mode 100755
index 0000000..b84345f
--- /dev/null
+++ b/sabel/db/migration/Query.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_Query
+{
+ private
+ $upgradeQueries = array(),
+ $downgradeQueries = array();
+
+ public function upgrade($query)
+ {
+ if (is_string($query)) {
+ $this->upgradeQueries[] = $query;
+ } else {
+ Sabel_Console::error("argument must be a string.");
+ exit;
+ }
+ }
+
+ public function downgrade($query)
+ {
+ if (is_string($query)) {
+ $this->downgradeQueries[] = $query;
+ } else {
+ Sabel_Console::error("argument must be a string.");
+ exit;
+ }
+ }
+
+ public function execute()
+ {
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ $queries = $this->upgradeQueries;
+ } else {
+ $queries = $this->downgradeQueries;
+ }
+
+ $stmt = Sabel_Db_Migration_Manager::getStatement();
+ foreach ($queries as $query) $stmt->setQuery($query)->execute();
+ }
+}
diff --git a/sabel/db/migration/Reader.php b/sabel/db/migration/Reader.php
new file mode 100755
index 0000000..2450334
--- /dev/null
+++ b/sabel/db/migration/Reader.php
@@ -0,0 +1,60 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_Reader extends Sabel_Object
+{
+ private $filePath = "";
+
+ public function __construct($filePath)
+ {
+ $this->filePath = $filePath;
+ }
+
+ public function readCreate()
+ {
+ $create = new Sabel_Db_Migration_Create();
+ include ($this->filePath);
+
+ return $create->build();
+ }
+
+ public function readAddColumn()
+ {
+ $add = new Sabel_Db_Migration_AddColumn();
+ include ($this->filePath);
+
+ return $add->build();
+ }
+
+ public function readDropColumn()
+ {
+ $drop = new Sabel_Db_Migration_DropColumn();
+ include ($this->filePath);
+
+ return $drop;
+ }
+
+ public function readChangeColumn()
+ {
+ $change = new Sabel_Db_Migration_ChangeColumn();
+ include ($this->filePath);
+
+ return $change;
+ }
+
+ public function readQuery()
+ {
+ $query = new Sabel_Db_Migration_Query();
+ include ($this->filePath);
+
+ return $query;
+ }
+}
diff --git a/sabel/db/migration/Writer.php b/sabel/db/migration/Writer.php
new file mode 100755
index 0000000..5037c9c
--- /dev/null
+++ b/sabel/db/migration/Writer.php
@@ -0,0 +1,132 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Migration_Writer
+{
+ private $fp = null;
+
+ public function __construct($filePath)
+ {
+ $this->fp = fopen($filePath, "w");
+ }
+
+ public function write($line)
+ {
+ fwrite($this->fp, $line);
+
+ return $this;
+ }
+
+ public function close()
+ {
+ fclose($this->fp);
+ }
+
+ public function writeTable($schema)
+ {
+ $fp = $this->fp;
+ $columns = $schema->getColumns();
+ $this->_writeColumns($columns, '$create');
+ $pkey = $schema->getPrimarykey();
+
+ if (is_array($pkey)) {
+ $pkeys = array();
+ foreach ($pkey as $key) $pkeys[] = '"' . $key .'"';
+ $this->write('$create->primary(array(' . implode(", ", $pkeys) . '));' . PHP_EOL);
+ }
+
+ $uniques = $schema->getUniques();
+
+ if ($uniques) {
+ foreach ($uniques as $unique) {
+ if (count($unique) === 1) {
+ $this->write('$create->unique("' . $unique[0] . '");');
+ } else {
+ $us = array();
+ foreach ($unique as $u) $us[] = '"' . $u . '"';
+ $this->write('$create->unique(array(' . implode(", ", $us) . '));');
+ }
+
+ $this->write(PHP_EOL);
+ }
+ }
+
+ if ($fkey = $schema->getForeignKey()) {
+ foreach ($fkey->toArray() as $colName => $param) {
+ $line = '$create->fkey("' . $colName . '")->table("'
+ . $param->table . '")->column("'
+ . $param->column . '")->onDelete("'
+ . $param->onDelete . '")->onUpdate("'
+ . $param->onUpdate . '");';
+
+ $this->write($line . PHP_EOL);
+ }
+ }
+
+ return $this;
+ }
+
+ public function writeColumns($schema, $alterCols, $variable = '$add')
+ {
+ $columns = array();
+
+ foreach ($schema->getColumns() as $column) {
+ if (in_array($column->name, $alterCols)) $columns[] = $column;
+ }
+
+ $this->_writeColumns($columns, $variable);
+
+ return $this;
+ }
+
+ private function _writeColumns($columns, $variable)
+ {
+ $lines = array();
+ foreach ($columns as $column) {
+ $line = array($variable);
+ $line[] = '->column("' . $column->name . '")';
+ $line[] = '->type(' . $column->type . ')';
+
+ $bool = ($column->nullable) ? "true" : "false";
+ $line[] = '->nullable(' . $bool . ')';
+
+ $bool = ($column->primary) ? "true" : "false";
+ $line[] = '->primary(' . $bool . ')';
+
+ $bool = ($column->increment) ? "true" : "false";
+ $line[] = '->increment(' . $bool . ')';
+
+ if ($column->default === null) {
+ $line[] = '->value(_NULL)';
+ } else {
+ if ($column->isNumeric()) {
+ $line[] = '->value(' . $column->default . ')';
+ } elseif ($column->isBool()) {
+ $bool = ($column->default) ? "true" : "false";
+ $line[] = '->value(' . $bool . ')';
+ } else {
+ $line[] = '->value("' . $column->default . '")';
+ }
+ }
+
+ if ($column->isString()) {
+ $line[] = '->length(' . $column->max. ')';
+ }
+
+ $line[] = ";";
+ $lines[] = implode("", $line);
+ }
+
+ $this->write("write(implode(PHP_EOL, $lines));
+ $this->write(PHP_EOL . PHP_EOL);
+ }
+}
diff --git a/sabel/db/model/CascadeDelete.php b/sabel/db/model/CascadeDelete.php
new file mode 100755
index 0000000..54fdbe4
--- /dev/null
+++ b/sabel/db/model/CascadeDelete.php
@@ -0,0 +1,114 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Model_CascadeDelete
+{
+ protected $model = null;
+ protected $keys = array();
+
+ protected $cascadeStack = array();
+
+ public function __construct($mdlName, $id)
+ {
+ $this->model = MODEL($mdlName, $id);
+ }
+
+ public function execute($config)
+ {
+ if (!is_object($config)) {
+ throw new Sabel_Db_Exception("argument should be an object of cascade delete config.");
+ }
+
+ $model = $this->model;
+ $cascade = $config->getChain();
+ $this->keys = $config->getKeys();
+ $mdlName = $model->getName();
+
+ Sabel_Db_Transaction::begin($model->getConnectionName());
+
+ $models = array();
+ $pKey = $model->getPrimaryKey();
+ $idValue = $model->$pKey;
+
+ $childNames = $cascade[$mdlName];
+
+ foreach ($childNames as $name) {
+ $keys = $this->getKeys($mdlName, $name, $pKey);
+ $results = $this->pushStack($name, $keys["fkey"], $idValue);
+ if ($results) $models[] = $results;
+ }
+
+ foreach ($models as $children) {
+ $this->makeChainModels($children, $cascade);
+ }
+
+ $this->clearCascadeStack();
+
+ $model->delete();
+ Sabel_Db_Transaction::commit();
+ }
+
+ private function makeChainModels($children, &$cascade)
+ {
+ $childObj = $children[0];
+ $mdlName = $childObj->getName();
+ if (!isset($cascade[$mdlName])) return;
+
+ $models = array();
+ $pKey = $childObj->getPrimaryKey();
+ $childNames = $cascade[$mdlName];
+
+ foreach ($childNames as $name) {
+ $keys = $this->getKeys($mdlName, $name, $pKey);
+ foreach ($children as $child) {
+ $results = $this->pushStack($name, $keys["fkey"], $child->$keys["id"]);
+ if ($results) $models[] = $results;
+ }
+ }
+ if (!$models) return;
+
+ foreach ($models as $children) {
+ $this->makeChainModels($children, $cascade);
+ }
+ }
+
+ protected function pushStack($child, $fkey, $idValue)
+ {
+ $model = MODEL($child);
+ $models = $model->select($fkey, $idValue);
+
+ if ($models) {
+ $this->cascadeStack["{$child}:{$idValue}"] = $fkey;
+ }
+
+ return $models;
+ }
+
+ protected function getKeys($parent, $child, $pKey)
+ {
+ if (isset($this->keys[$parent][$child])) {
+ return $this->keys[$parent][$child];
+ } else {
+ $tblName = convert_to_tablename($parent);
+ return array("id" => "id", "fkey" => "{$tblName}_id");
+ }
+ }
+
+ private function clearCascadeStack()
+ {
+ $stack = array_reverse($this->cascadeStack);
+
+ foreach ($stack as $param => $fkey) {
+ list($mdlName, $idValue) = explode(":", $param);
+ MODEL($mdlName)->delete($fkey, $idValue);
+ }
+ }
+}
diff --git a/sabel/db/model/Proxy.php b/sabel/db/model/Proxy.php
new file mode 100755
index 0000000..bca11ee
--- /dev/null
+++ b/sabel/db/model/Proxy.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Model_Proxy extends Sabel_Db_Model
+{
+ public function __construct($mdlName, $id)
+ {
+ $this->initialize($mdlName);
+
+ if ($id !== null) {
+ $this->setCondition($id);
+ $this->_doSelectOne($this);
+ }
+ }
+}
diff --git a/sabel/db/mssql/Blob.php b/sabel/db/mssql/Blob.php
new file mode 100755
index 0000000..3b67666
--- /dev/null
+++ b/sabel/db/mssql/Blob.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mssql_Blob extends Sabel_Db_Abstract_Blob
+{
+ public function __construct($binary)
+ {
+ $this->binary = $binary;
+ }
+
+ public function getData()
+ {
+ return "0x" . bin2hex($this->binary);
+ }
+}
diff --git a/sabel/db/mssql/Driver.php b/sabel/db/mssql/Driver.php
new file mode 100755
index 0000000..136dac2
--- /dev/null
+++ b/sabel/db/mssql/Driver.php
@@ -0,0 +1,88 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mssql_Driver extends Sabel_Db_Driver
+{
+ public function connect(array $params)
+ {
+ $host = $params["host"];
+ $host = (isset($params["port"])) ? $host . "," . $params["port"] : $host;
+ $conn = mssql_connect($host, $params["user"], $params["password"], true);
+
+ if ($conn) {
+ mssql_select_db($params["database"], $conn);
+ return $conn;
+ } else {
+ return mssql_get_last_message();
+ }
+ }
+
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel !== null) {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ $this->execute("BEGIN TRANSACTION");
+ $this->autoCommit = false;
+ return $this->connection;
+ }
+
+ public function commit()
+ {
+ $this->execute("COMMIT TRANSACTION");
+ $this->autoCommit = true;
+ }
+
+ public function rollback()
+ {
+ $this->execute("ROLLBACK TRANSACTION");
+ $this->autoCommit = true;
+ }
+
+ public function close($connection)
+ {
+ mssql_close($connection);
+ unset($this->connection);
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $sql = $this->bind($sql, $bindParams);
+
+ if ($result = mssql_query($sql, $this->connection)) {
+ $rows = array();
+ if (is_resource($result)) {
+ while ($row = mssql_fetch_assoc($result)) $rows[] = $row;
+ mssql_free_result($result);
+ $this->affectedRows = 0;
+ } else {
+ $this->affectedRows = mssql_rows_affected($this->connection);
+ }
+
+ return (empty($rows)) ? null : $rows;
+ } else {
+ $this->executeError($sql);
+ }
+ }
+
+ public function getLastInsertId()
+ {
+ $rows = $this->execute("SELECT SCOPE_IDENTITY() AS id");
+ return $rows[0]["id"];
+ }
+
+ private function executeError($sql)
+ {
+ $error = mssql_get_last_message();
+ throw new Sabel_Db_Exception_Driver("{$error}, SQL: $sql");
+ }
+}
diff --git a/sabel/db/mssql/Metadata.php b/sabel/db/mssql/Metadata.php
new file mode 100755
index 0000000..8d67872
--- /dev/null
+++ b/sabel/db/mssql/Metadata.php
@@ -0,0 +1,201 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mssql_Metadata extends Sabel_Db_Abstract_Metadata
+{
+ /**
+ * @var array
+ */
+ private $sequences = array();
+
+ /**
+ * @var array
+ */
+ private $primaryKeys = array();
+
+ public function getTableList()
+ {
+ $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES "
+ . "WHERE table_schema = '{$this->schemaName}'";
+
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $tables = array();
+ foreach ($rows as $row) {
+ $tables[] = $row["table_name"];
+ }
+
+ return $tables;
+ }
+
+ protected function createColumns($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $this->createSequences($tblName);
+ $this->createPrimaryKeys($tblName);
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $colName = $row["column_name"];
+ $columns[$colName] = $this->createColumn($row);
+ }
+
+ return $columns;
+ }
+
+ protected function createColumn($row)
+ {
+ $column = new Sabel_Db_Metadata_Column();
+ $column->name = $row["column_name"];
+ $column->nullable = ($row["is_nullable"] !== "NO");
+
+ if ($row["data_type"] === "varchar" && $row["character_maximum_length"] === -1) {
+ $row["data_type"] = "text";
+ } elseif ($row["data_type"] === "float") {
+ $row["data_type"] = "double";
+ }
+
+ Sabel_Db_Type_Manager::create()->applyType($column, $row["data_type"]);
+ $this->setDefault($column, $row["column_default"]);
+
+ $column->primary = (in_array($column->name, $this->primaryKeys));
+ $column->increment = (in_array($column->name, $this->sequences));
+
+ if ($column->primary) $column->nullable = false;
+ if ($column->isString()) $this->setLength($column, $row);
+
+ return $column;
+ }
+
+ public function getForeignKeys($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $column = $row["column_name"];
+ $columns[$column]["referenced_table"] = $row["ref_table"];
+ $columns[$column]["referenced_column"] = $row["ref_column"];
+ $columns[$column]["on_delete"] = $row["delete_rule"];
+ $columns[$column]["on_update"] = $row["update_rule"];
+ }
+
+ return $columns;
+ }
+
+ public function getUniques($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $uniques = array();
+ foreach ($rows as $row) {
+ $key = $row["constraint_name"];
+ $uniques[$key][] = $row["column_name"];
+ }
+
+ return array_values($uniques);
+ }
+
+ private function createSequences($tblName)
+ {
+ if (!empty($this->sequences)) return;
+
+ $sql = <<driver->execute($sql);
+ if (!$rows) return;
+
+ $seqs =& $this->sequences;
+ foreach ($rows as $row) $seqs[] = $row["name"];
+ }
+
+ private function createPrimaryKeys($tblName)
+ {
+ if (!empty($this->primaryKeys)) return;
+
+ $sql = <<driver->execute($sql);
+ if (!$rows) return;
+
+ $keys =& $this->primaryKeys;
+ foreach ($rows as $row) $keys[] = $row["column_name"];
+ }
+
+ private function setDefault($column, $default)
+ {
+ if ($default === null) {
+ $column->default = null;
+ } else {
+ $this->setDefaultValue($column, substr($default, 2, -2));
+ }
+ }
+
+ private function setLength($column, $row)
+ {
+ $maxlen = $row["character_maximum_length"];
+ $column->max = ($maxlen === null) ? 255 : (int)$maxlen;
+ }
+}
diff --git a/sabel/db/mssql/Migration.php b/sabel/db/mssql/Migration.php
new file mode 100755
index 0000000..06837bb
--- /dev/null
+++ b/sabel/db/mssql/Migration.php
@@ -0,0 +1,158 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mssql_Migration extends Sabel_Db_Abstract_Migration
+{
+ protected $types = array(Sabel_Db_Type::INT => "INTEGER",
+ Sabel_Db_Type::BIGINT => "BIGINT",
+ Sabel_Db_Type::SMALLINT => "SMALLINT",
+ Sabel_Db_Type::FLOAT => "REAL",
+ Sabel_Db_Type::DOUBLE => "DOUBLE PRECISION",
+ Sabel_Db_Type::BOOL => "BIT",
+ Sabel_Db_Type::STRING => "VARCHAR",
+ Sabel_Db_Type::TEXT => "VARCHAR(MAX)",
+ Sabel_Db_Type::DATETIME => "DATETIME",
+ Sabel_Db_Type::DATE => "DATETIME",
+ Sabel_Db_Type::BINARY => "VARBINARY(MAX)");
+
+ protected function createTable($filePath)
+ {
+ $query = $this->getCreateSql($this->getReader($filePath)->readCreate());
+ $this->executeQuery($query);
+ }
+
+ protected function addColumn()
+ {
+ $columns = $this->getReader()->readAddColumn()->getColumns();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ $this->execAddColumn($columns);
+ } else {
+ $tblName = convert_to_tablename($this->mdlName);
+ $quotedTblName = $this->quoteIdentifier($tblName);
+ foreach ($columns as $column) {
+ $this->dropDefaultConstraint($tblName, $column->name);
+ $colName = $this->quoteIdentifier($column->name);
+ $this->executeQuery("ALTER TABLE $tblName DROP COLUMN $colName");
+ }
+ }
+ }
+
+ protected function changeColumnUpgrade($columns, $schema)
+ {
+ $tblName = $this->quoteIdentifier($schema->getTableName());
+
+ foreach ($columns as $column) {
+ $current = $schema->getColumnByName($column->name);
+ $line = $this->alterChange($column, $current);
+ $this->executeQuery("ALTER TABLE $tblName ALTER COLUMN $line");
+ }
+ }
+
+ protected function changeColumnDowngrade($columns, $schema)
+ {
+ $tblName = $this->quoteIdentifier($schema->getTableName());
+
+ foreach ($columns as $column) {
+ $line = $this->createColumnAttributes($column);
+ $this->executeQuery("ALTER TABLE $tblName ALTER COLUMN $line");
+ }
+ }
+
+ protected function alterChange($column, $current)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($column->name);
+
+ $c = ($column->type === null) ? $current : $column;
+ $line[] = $this->getDataType($c, false);
+
+ if ($c->isString()) {
+ $max = ($column->max === null) ? $current->max : $column->max;
+ $line[] = "({$max})";
+ }
+
+ $c = ($column->nullable === null) ? $current : $column;
+ $line[] = ($c->nullable === false) ? "NOT NULL" : "NULL";
+
+ /* @todo reset default constraint
+ if (($d = $column->default) !== _NULL) {
+ $cd = $current->default;
+
+ if ($d === $cd) {
+ $line[] = $this->getDefaultValue($current);
+ } else {
+ $this->valueCheck($column, $d);
+ $line[] = $this->getDefaultValue($column);
+ }
+ }
+ */
+
+ if ($column->increment) $line[] = "IDENTITY(1, 1)";
+ return implode(" ", $line);
+ }
+
+ protected function createColumnAttributes($column)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($column->name);
+ $line[] = $this->getDataType($column);
+ $line[] = ($column->nullable === false) ? "NOT NULL" : "NULL";
+ $line[] = $this->getDefaultValue($column);
+
+ if ($column->increment) $line[] = "IDENTITY(1, 1)";
+ return implode(" ", $line);
+ }
+
+ private function getDataType($col, $withLength = true)
+ {
+ if (!$withLength) return $this->types[$col->type];
+
+ if ($col->isString()) {
+ return $this->types[$col->type] . "({$col->max})";
+ } else {
+ return $this->types[$col->type];
+ }
+ }
+
+ protected function getBooleanAttr($value)
+ {
+ $v = ($value === true) ? "1" : "0";
+ return "DEFAULT " . $v;
+ }
+
+ protected function dropDefaultConstraint($tblName, $colName)
+ {
+ $connectionName = $this->getStatement()->getDriver()->getConnectionName();
+ $schemaName = Sabel_Db_Config::getSchemaName($connectionName);
+ $cName = $this->getDefaultConstraintName($schemaName, $tblName, $colName);
+ if ($cName === null) return;
+
+ $quotedTblName = $this->quoteIdentifier($tblName);
+ $this->executeQuery("ALTER TABLE $quotedTblName DROP CONSTRAINT $cName");
+ }
+
+ protected function getDefaultConstraintName($schemaName, $tblName, $colName)
+ {
+ $sql = <<executeQuery($sql);
+ return (isset($rows[0]["name"])) ? $rows[0]["name"] : null;
+ }
+}
diff --git a/sabel/db/mssql/Statement.php b/sabel/db/mssql/Statement.php
new file mode 100755
index 0000000..7820b46
--- /dev/null
+++ b/sabel/db/mssql/Statement.php
@@ -0,0 +1,159 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mssql_Statement extends Sabel_Db_Statement
+{
+ public function __construct(Sabel_Db_Mssql_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array())
+ {
+ $result = parent::execute($bindValues);
+
+ if (!$this->isSelect() || empty($result) || !extension_loaded("mbstring")) {
+ return $result;
+ }
+
+ $fromEnc = (defined("SDB_MSSQL_ENCODING")) ? SDB_MSSQL_ENCODING : "SJIS";
+ $toEnc = mb_internal_encoding();
+ $columns = $this->metadata->getColumns();
+
+ foreach ($result as &$row) {
+ foreach ($columns as $name => $column) {
+ if (isset($row[$name]) && ($column->isString() || $column->isText())) {
+ $row[$name] = mb_convert_encoding($row[$name], $toEnc, $fromEnc);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function escape(array $values)
+ {
+ if (extension_loaded("mbstring")) {
+ $toEnc = (defined("SDB_MSSQL_ENCODING")) ? SDB_MSSQL_ENCODING : "SJIS";
+ $fromEnc = mb_internal_encoding();
+
+ $currentRegexEnc = mb_regex_encoding();
+ mb_regex_encoding($fromEnc);
+
+ foreach ($values as $k => &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? "1" : "0";
+ } elseif (is_string($val)) {
+ $val = "'" . mb_convert_encoding(mb_ereg_replace("'", "''", $val), $toEnc, $fromEnc) . "'";
+ }
+ }
+
+ mb_regex_encoding($currentRegexEnc);
+ } else {
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? "1" : "0";
+ } elseif (is_string($val)) {
+ $val = "'" . str_replace("'", "''", $val) . "'";
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ public function createBlob($binary)
+ {
+ return new Sabel_Db_Mssql_Blob($binary);
+ }
+
+ protected function createSelectSql()
+ {
+ $tblName = $this->quoteIdentifier($this->table);
+ $projection = $this->getProjection();
+ $c = $this->constraints;
+
+ $limit = null;
+ $offset = null;
+
+ if (isset($c["offset"]) && isset($c["limit"])) {
+ $limit = $c["limit"];
+ $offset = $c["offset"];
+ } elseif (isset($c["offset"]) && !isset($c["limit"])) {
+ $limit = 100;
+ $offset = $c["offset"];
+ } elseif (isset($c["limit"]) && !isset($c["offset"])) {
+ $limit = $c["limit"];
+ $offset = 0;
+ }
+
+ if ($limit !== null) {
+ if (isset($c["order"])) {
+ $order = $c["order"];
+ } else {
+ $order = convert_to_modelname($this->metadata->getTableName()) . "."
+ . $this->metadata->getPrimaryKey() . " ASC";
+ }
+
+ $orderBy = " ORDER BY " . $this->quoteIdentifierForOrderBy($order);
+ $sql = "SELECT * FROM (SELECT ROW_NUMBER() OVER({$orderBy}) AS [SBL_RN], $projection "
+ . "FROM $tblName" . $this->join . $this->where . ") AS [SBL_TMP] "
+ . "WHERE [SBL_RN] BETWEEN " . ($offset + 1) . " AND "
+ . ($offset + $limit) . " " . $orderBy;
+ } else {
+ $sql = "SELECT $projection FROM $tblName" . $this->join . $this->where;
+
+ if (isset($c["order"])) {
+ $sql .= " ORDER BY " . $this->quoteIdentifierForOrderBy($c["order"]);
+ }
+ }
+
+ return $sql;
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '[' . $v . ']';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '[' . $arg . ']';
+ } else {
+ $message = __METHOD__ . "() argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ // @todo nulls
+ protected function quoteIdentifierForOrderBy($orders)
+ {
+ $results = array();
+ foreach ($orders as $column => $order) {
+ $mode = strtoupper($order["mode"]);
+ //$nulls = strtoupper($order["nulls"]);
+
+ if (($pos = strpos($column, ".")) !== false) {
+ $tblName = convert_to_tablename(substr($column, 0, $pos));
+ $column = $this->quoteIdentifier($tblName) . "."
+ . $this->quoteIdentifier(substr($column, $pos + 1));
+ } else {
+ $column = $this->quoteIdentifier($column);
+ }
+
+ //$_nulls = ($nulls === "FIRST") ? "IS NOT NULL" : "IS NULL";
+ $results[] = "{$column} {$mode}";
+ }
+
+ return implode(", ", $results);
+ }
+}
diff --git a/sabel/db/mysql/Blob.php b/sabel/db/mysql/Blob.php
new file mode 100755
index 0000000..596ba7e
--- /dev/null
+++ b/sabel/db/mysql/Blob.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysql_Blob extends Sabel_Db_Abstract_Blob
+{
+ protected $conn = null;
+
+ public function __construct($conn, $binary)
+ {
+ $this->conn = $conn;
+ $this->binary = $binary;
+ }
+
+ public function getData()
+ {
+ return "'" . mysql_real_escape_string($this->binary, $this->conn) . "'";
+ }
+}
diff --git a/sabel/db/mysql/Driver.php b/sabel/db/mysql/Driver.php
new file mode 100755
index 0000000..e98aae2
--- /dev/null
+++ b/sabel/db/mysql/Driver.php
@@ -0,0 +1,96 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysql_Driver extends Sabel_Db_Driver
+{
+ public function connect(array $params)
+ {
+ $host = $params["host"];
+ $host = (isset($params["port"])) ? $host . ":" . $params["port"] : $host;
+ $conn = mysql_connect($host, $params["user"], $params["password"], true);
+
+ if ($conn) {
+ if (!mysql_select_db($params["database"], $conn)) {
+ return mysql_error();
+ }
+
+ if (isset($params["charset"])) {
+ if (function_exists("mysql_set_charset")) {
+ mysql_set_charset($params["charset"], $conn);
+ } else {
+ mysql_query("SET NAMES " . $params["charset"], $conn);
+ }
+ }
+
+ return $conn;
+ } else {
+ return mysql_error();
+ }
+ }
+
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel !== null) {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ $this->execute("START TRANSACTION");
+ $this->autoCommit = false;
+ return $this->connection;
+ }
+
+ public function commit()
+ {
+ $this->execute("COMMIT");
+ $this->autoCommit = true;
+ }
+
+ public function rollback()
+ {
+ $this->execute("ROLLBACK");
+ $this->autoCommit = true;
+ }
+
+ public function close($connection)
+ {
+ mysql_close($connection);
+ unset($this->connection);
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $sql = $this->bind($sql, $bindParams);
+ $result = mysql_query($sql, $this->connection);
+ if (!$result) $this->executeError($sql);
+
+ $rows = array();
+ if (is_resource($result)) {
+ while ($row = mysql_fetch_assoc($result)) $rows[] = $row;
+ mysql_free_result($result);
+ $this->affectedRows = 0;
+ } else {
+ $this->affectedRows = mysql_affected_rows($this->connection);
+ }
+
+ return (empty($rows)) ? null : $rows;
+ }
+
+ public function getLastInsertId()
+ {
+ return mysql_insert_id($this->connection);
+ }
+
+ private function executeError($sql)
+ {
+ $error = mysql_error($this->connection);
+ throw new Sabel_Db_Exception_Driver("{$error}, SQL: $sql");
+ }
+}
diff --git a/sabel/db/mysql/Metadata.php b/sabel/db/mysql/Metadata.php
new file mode 100755
index 0000000..bd0c1f7
--- /dev/null
+++ b/sabel/db/mysql/Metadata.php
@@ -0,0 +1,222 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysql_Metadata extends Sabel_Db_Abstract_Metadata
+{
+ public function getTable($tblName)
+ {
+ $schema = parent::getTable($tblName);
+ $schema->setTableEngine($this->getTableEngine($tblName));
+
+ return $schema;
+ }
+
+ public function getTableList()
+ {
+ $sql = "SELECT table_name FROM information_schema.tables "
+ . "WHERE table_schema = '{$this->schemaName}'";
+
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $tables = array();
+ foreach ($rows as $row) {
+ $row = array_change_key_case($row);
+ $tables[] = $row["table_name"];
+ }
+
+ return $tables;
+ }
+
+ protected function createColumns($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $colName = $row["column_name"];
+ $columns[$colName] = $this->createColumn($row);
+ }
+
+ return $columns;
+ }
+
+ protected function createColumn($row)
+ {
+ $column = new Sabel_Db_Metadata_Column();
+ $column->name = $row["column_name"];
+ $column->nullable = ($row["is_nullable"] !== "NO");
+
+ if ($row["column_type"] === "tinyint(1)") {
+ $column->type = Sabel_Db_Type::BOOL;
+ } else {
+ Sabel_Db_Type_Manager::create()->applyType($column, $row["data_type"]);
+ }
+
+ $this->setDefault($column, $row["column_default"]);
+ $column->primary = ($row["column_key"] === "PRI");
+ $column->increment = ($row["extra"] === "auto_increment");
+
+ if ($column->primary) {
+ $column->nullable = false;
+ }
+
+ if ($column->isString()) {
+ $column->max = (int)$row["character_maximum_length"];
+ }
+
+ return $column;
+ }
+
+ public function getForeignKeys($tblName)
+ {
+ $exp = explode(".", $this->getMysqlVersion());
+
+ if (isset($exp[1]) && $exp[1] === "0") {
+ return $this->getForeignKeys50($tblName);
+ } else {
+ return $this->getForeignKeys51($tblName);
+ }
+ }
+
+ public function getUniques($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $uniques = array();
+ foreach ($rows as $row) {
+ $key = $row["unique_key"];
+ $uniques[$key][] = $row["column_name"];
+ }
+
+ return array_values($uniques);
+ }
+
+ public function getTableEngine($tblName)
+ {
+ $rows = $this->driver->execute("SHOW TABLE STATUS WHERE Name='{$tblName}'");
+ return $rows[0]["Engine"];
+ }
+
+ protected function getMysqlVersion()
+ {
+ $rows = $this->driver->execute("SELECT VERSION() AS version");
+ return $rows[0]["version"];
+ }
+
+ private function getForeignKeys50($tblName)
+ {
+ $schemaName = $this->schemaName;
+ $result = $this->driver->execute("SHOW CREATE TABLE `{$tblName}`");
+ if (!isset($result[0]["Create Table"])) return null;
+
+ $createSql = $result[0]["Create Table"];
+ preg_match_all("/CONSTRAINT .+ FOREIGN KEY (.+)/", $createSql, $matches);
+ if (empty($matches[1])) return null;
+
+ $columns = array();
+ foreach ($matches[1] as $match) {
+ $tmp = explode(")", str_replace(" REFERENCES ", "", $match));
+ $column = substr($tmp[0], 2, -1);
+
+ $tmp2 = array_map("trim", explode("(", $tmp[1]));
+ $columns[$column]["referenced_table"] = substr($tmp2[0], 1, -1);
+ $columns[$column]["referenced_column"] = substr($tmp2[1], 1, -1);
+
+ $rule = trim($tmp[2]);
+ $columns[$column]["on_delete"] = $this->getRule($rule, "ON DELETE");
+ $columns[$column]["on_update"] = $this->getRule($rule, "ON UPDATE");
+ }
+
+ return $columns;
+ }
+
+ private function getRule($rule, $ruleName)
+ {
+ if (($pos = strpos($rule, $ruleName)) !== false) {
+ $on = substr($rule, $pos + 10);
+ if (($pos = strpos($on, " ON")) !== false) {
+ $on = substr($on, 0, $pos);
+ }
+
+ return trim(str_replace(",", "", $on));
+ } else {
+ return "NO ACTION";
+ }
+ }
+
+ private function getForeignKeys51($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $column = $row["column_name"];
+ $columns[$column]["referenced_table"] = $row["ref_table"];
+ $columns[$column]["referenced_column"] = $row["ref_column"];
+ $columns[$column]["on_delete"] = $row["delete_rule"];
+ $columns[$column]["on_update"] = $row["update_rule"];
+ }
+
+ return $columns;
+ }
+
+ private function setDefault($co, $default)
+ {
+ if ($default === null) {
+ $co->default = null;
+ } else {
+ $this->setDefaultValue($co, $default);
+ }
+ }
+}
diff --git a/sabel/db/mysql/Migration.php b/sabel/db/mysql/Migration.php
new file mode 100755
index 0000000..6700359
--- /dev/null
+++ b/sabel/db/mysql/Migration.php
@@ -0,0 +1,181 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysql_Migration extends Sabel_Db_Abstract_Migration
+{
+ protected $types = array(Sabel_Db_Type::INT => "INTEGER",
+ Sabel_Db_Type::BIGINT => "BIGINT",
+ Sabel_Db_Type::SMALLINT => "SMALLINT",
+ Sabel_Db_Type::FLOAT => "FLOAT",
+ Sabel_Db_Type::DOUBLE => "DOUBLE",
+ Sabel_Db_Type::BOOL => "TINYINT(1)",
+ Sabel_Db_Type::STRING => "VARCHAR",
+ Sabel_Db_Type::TEXT => "TEXT",
+ Sabel_Db_Type::DATETIME => "DATETIME",
+ Sabel_Db_Type::DATE => "DATE",
+ Sabel_Db_Type::BINARY => "LONGBLOB");
+
+ protected function createTable($filePath)
+ {
+ $create = $this->getReader($filePath)->readCreate();
+ $query = $this->getCreateSql($create);
+ $options = $create->getOptions();
+
+ if (isset($options["engine"])) {
+ $query .= " ENGINE=" . $options["engine"];
+ }
+
+ $this->executeQuery($query);
+
+ if ($indexes = $create->getIndexes()) {
+ $this->createIndex($indexes);
+ }
+ }
+
+ public function drop()
+ {
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ $restore = $this->getRestoreFileName();
+ if (is_file($restore)) unlink($restore);
+
+ $schema = $this->getSchema();
+ $tblSchema = $schema->getTable($this->tblName);
+ $engine = $schema->getTableEngine($this->tblName);
+
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeTable($tblSchema);
+ $writer->write('$create->options("engine", "' . $engine . '");');
+ $writer->write(PHP_EOL)->close();
+
+ $this->executeQuery("DROP TABLE " . $this->quoteIdentifier($this->tblName));
+ } else {
+ $this->createTable($this->getRestoreFileName());
+ }
+ }
+
+ protected function changeColumnUpgrade($columns, $schema)
+ {
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+
+ foreach ($columns as $column) {
+ $current = $schema->getColumnByName($column->name);
+ $line = $this->alterChange($column, $current);
+ $this->executeQuery("ALTER TABLE $quotedTblName MODIFY $line");
+ }
+ }
+
+ protected function changeColumnDowngrade($columns, $schema)
+ {
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+
+ foreach ($columns as $column) {
+ $line = $this->createColumnAttributes($column);
+ $this->executeQuery("ALTER TABLE $quotedTblName MODIFY $line");
+ }
+ }
+
+ protected function createColumnAttributes($col)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($col->name);
+ $line[] = $this->getTypeDefinition($col);
+ $line[] = $this->getNullableDefinition($col);
+ $line[] = $this->getDefaultValue($col);
+
+ if ($col->increment) $line[] = "AUTO_INCREMENT";
+ return implode(" ", $line);
+ }
+
+ protected function alterChange($column, $current)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($column->name);
+
+ $c = ($column->type === null) ? $current : $column;
+ $line[] = $this->getTypeDefinition($c, false);
+
+ if ($c->isString()) {
+ $max = ($column->max === null) ? $current->max : $column->max;
+ $line[] = "({$max})";
+ }
+
+ $c = ($column->nullable === null) ? $current : $column;
+ $line[] = $this->getNullableDefinition($c);
+
+ if (($d = $column->default) !== _NULL) {
+ $cd = $current->default;
+
+ if ($d === $cd) {
+ $line[] = $this->getDefaultValue($current);
+ } else {
+ $this->valueCheck($column, $d);
+ $line[] = $this->getDefaultValue($column);
+ }
+ }
+
+ if ($column->increment) $line[] = "AUTO_INCREMENT";
+ return implode(" ", $line);
+ }
+
+ protected function getTypeDefinition($col, $withLength = true)
+ {
+ if (!$withLength) return $this->types[$col->type];
+
+ if ($col->isString()) {
+ return $this->types[$col->type] . "({$col->max})";
+ } else {
+ return $this->types[$col->type];
+ }
+ }
+
+ protected function getNullableDefinition($column)
+ {
+ return ($column->nullable === false) ? "NOT NULL" : "";
+ }
+
+ protected function getDefaultValue($column)
+ {
+ $d = $column->default;
+
+ if ($column->isBool()) {
+ return $this->getBooleanAttr($d);
+ } elseif ($d === null || $d === _NULL) {
+ if ($column->isString()) {
+ return ($column->nullable === true) ? "DEFAULT ''" : "";
+ } else {
+ return ($column->nullable === true) ? "DEFAULT NULL" : "";
+ }
+ } elseif ($column->isNumeric()) {
+ return "DEFAULT $d";
+ } else {
+ return "DEFAULT '{$d}'";
+ }
+ }
+
+ protected function getBooleanAttr($value)
+ {
+ $value = ($value === true) ? "1" : "0";
+ return "DEFAULT " . $value;
+ }
+
+ private function valueCheck($column, $default)
+ {
+ if ($default === null) return true;
+
+ if (($column->isBool() && !is_bool($default)) ||
+ ($column->isNumeric() && !is_numeric($default))) {
+ $message = __METHOD__ . "() invalid default value for '{$column->name}'.";
+ throw new Sabel_Db_Exception($message);
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/sabel/db/mysql/Statement.php b/sabel/db/mysql/Statement.php
new file mode 100755
index 0000000..9d26268
--- /dev/null
+++ b/sabel/db/mysql/Statement.php
@@ -0,0 +1,107 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysql_Statement extends Sabel_Db_Statement
+{
+ protected $binaries = array();
+
+ public function __construct(Sabel_Db_Mysql_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function clear()
+ {
+ $this->binaries = array();
+ return parent::clear();
+ }
+
+ public function values(array $values)
+ {
+ $columns = $this->metadata->getColumns();
+
+ if ($this->isInsert()) {
+ foreach ($columns as $colName => $column) {
+ if (!isset($values[$colName]) && $this->isVarcharOfDefaultNull($column)) {
+ $values[$colName] = null;
+ }
+ }
+ }
+
+ foreach ($values as $k => &$v) {
+ if (isset($columns[$k]) && $columns[$k]->isBinary()) {
+ $this->binaries[] = $this->createBlob($v);
+ $v = new Sabel_Db_Statement_Expression($this, "__sbl_binary" . count($this->binaries));
+ }
+ }
+
+ $this->values = $values;
+ $this->binds($values);
+
+ return $this;
+ }
+
+ public function escape(array $values)
+ {
+ $conn = $this->driver->getConnection();
+
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? 1 : 0;
+ } elseif (is_string($val)) {
+ $val = "'" . mysql_real_escape_string($val, $conn) . "'";
+ }
+ }
+
+ return $values;
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ $query = $this->getQuery();
+
+ if (!empty($this->binaries)) {
+ for ($i = 0, $c = count($this->binaries); $i < $c; $i++) {
+ $query = str_replace("__sbl_binary" . ($i + 1),
+ $this->binaries[$i]->getData(),
+ $query);
+ }
+ }
+
+ return parent::execute($bindValues, $additionalParameters, $query);
+ }
+
+ public function createBlob($binary)
+ {
+ $conn = $this->driver->getConnection();
+ return new Sabel_Db_Mysql_Blob($conn, $binary);
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '`' . $v . '`';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '`' . $arg . '`';
+ } else {
+ $message = "argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ private function isVarcharOfDefaultNull($column)
+ {
+ return ($column->isString() && $column->default === null);
+ }
+}
diff --git a/sabel/db/mysqli/Blob.php b/sabel/db/mysqli/Blob.php
new file mode 100755
index 0000000..306027f
--- /dev/null
+++ b/sabel/db/mysqli/Blob.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysqli_Blob extends Sabel_Db_Mysql_Blob
+{
+ public function getData()
+ {
+ return "'" . mysqli_real_escape_string($this->conn, $this->binary) . "'";
+ }
+}
diff --git a/sabel/db/mysqli/Driver.php b/sabel/db/mysqli/Driver.php
new file mode 100755
index 0000000..5d18bad
--- /dev/null
+++ b/sabel/db/mysqli/Driver.php
@@ -0,0 +1,103 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysqli_Driver extends Sabel_Db_Driver
+{
+ public function connect(array $params)
+ {
+ $h = $params["host"];
+ $u = $params["user"];
+ $p = $params["password"];
+ $d = $params["database"];
+
+ if (isset($params["port"])) {
+ $conn = mysqli_connect($h, $u, $p, $d, (int)$params["port"]);
+ } else {
+ $conn = mysqli_connect($h, $u, $p, $d);
+ }
+
+ if ($conn) {
+ if (isset($params["charset"])) {
+ mysqli_set_charset($conn, $params["charset"]);
+ }
+
+ return $conn;
+ } else {
+ return mysqli_connect_error();
+ }
+ }
+
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel !== null) {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ if (mysqli_autocommit($this->connection, $this->autoCommit = false)) {
+ return $this->connection;
+ } else {
+ throw new Sabel_Db_Exception_Driver(mysql_error($this->connection));
+ }
+ }
+
+ public function commit()
+ {
+ if (mysqli_commit($this->connection)) {
+ mysqli_autocommit($this->connection, $this->autoCommit = true);
+ } else {
+ throw new Sabel_Db_Exception_Driver(mysql_error($this->connection));
+ }
+ }
+
+ public function rollback()
+ {
+ if (mysqli_rollback($this->connection)) {
+ mysqli_autocommit($this->connection, $this->autoCommit = true);
+ } else {
+ throw new Sabel_Db_Exception_Driver(mysql_error($this->connection));
+ }
+ }
+
+ public function close($connection)
+ {
+ mysqli_close($connection);
+ unset($this->connection);
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $sql = $this->bind($sql, $bindParams);
+ $result = mysqli_query($this->connection, $sql);
+ if (!$result) $this->executeError($sql);
+
+ $rows = array();
+ if (is_object($result)) {
+ while ($row = mysqli_fetch_assoc($result)) $rows[] = $row;
+ mysqli_free_result($result);
+ $this->affectedRows = 0;
+ } else {
+ $this->affectedRows = mysqli_affected_rows($this->connection);
+ }
+
+ return (empty($rows)) ? null : $rows;
+ }
+
+ public function getLastInsertId()
+ {
+ return mysqli_insert_id($this->connection);
+ }
+
+ private function executeError($sql)
+ {
+ $error = mysqli_error($this->connection);
+ throw new Sabel_Db_Exception_Driver("{$error}, SQL: $sql");
+ }
+}
diff --git a/sabel/db/mysqli/Migration.php b/sabel/db/mysqli/Migration.php
new file mode 100755
index 0000000..0ff9625
--- /dev/null
+++ b/sabel/db/mysqli/Migration.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysqli_Migration extends Sabel_Db_Mysql_Migration
+{
+
+}
diff --git a/sabel/db/mysqli/Statement.php b/sabel/db/mysqli/Statement.php
new file mode 100755
index 0000000..2b0d954
--- /dev/null
+++ b/sabel/db/mysqli/Statement.php
@@ -0,0 +1,39 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Mysqli_Statement extends Sabel_Db_Mysql_Statement
+{
+ public function __construct(Sabel_Db_Mysqli_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function escape(array $values)
+ {
+ $conn = $this->driver->getConnection();
+
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? 1 : 0;
+ } elseif (is_string($val)) {
+ $val = "'" . mysqli_real_escape_string($conn, $val) . "'";
+ }
+ }
+
+ return $values;
+ }
+
+ public function createBlob($binary)
+ {
+ $conn = $this->driver->getConnection();
+ return new Sabel_Db_Mysqli_Blob($conn, $binary);
+ }
+}
diff --git a/sabel/db/oci/Blob.php b/sabel/db/oci/Blob.php
new file mode 100755
index 0000000..426ac38
--- /dev/null
+++ b/sabel/db/oci/Blob.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Oci_Blob extends Sabel_Db_Abstract_Blob
+{
+ protected $conn = null;
+ protected $lob = null;
+
+ public function __construct($conn, $binary)
+ {
+ $this->conn = $conn;
+ $this->binary = $binary;
+ $this->lob = oci_new_descriptor($conn, OCI_D_LOB);
+ }
+
+ public function getData()
+ {
+ return $this->binary;
+ }
+
+ public function getLob()
+ {
+ return $this->lob;
+ }
+
+ public function save()
+ {
+ $this->lob->save($this->getData());
+ }
+}
diff --git a/sabel/db/oci/Driver.php b/sabel/db/oci/Driver.php
new file mode 100755
index 0000000..6ef1927
--- /dev/null
+++ b/sabel/db/oci/Driver.php
@@ -0,0 +1,167 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Oci_Driver extends Sabel_Db_Driver
+{
+ /**
+ * @var int
+ */
+ private $limit = null;
+
+ /**
+ * @var int
+ */
+ private $offset = null;
+
+ /**
+ * @var int
+ */
+ private $lastInsertId = null;
+
+ public function connect(array $params)
+ {
+ $database = "//" . $params["host"] . "/" . $params["database"];
+ $encoding = (isset($params["charset"])) ? $params["charset"] : null;
+
+ $conn = oci_new_connect($params["user"], $params["password"], $database, $encoding);
+
+ if ($conn) {
+ $stmt = oci_parse($conn, "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'");
+ oci_execute($stmt, OCI_COMMIT_ON_SUCCESS);
+ return $conn;
+ } else {
+ $e = oci_error();
+ return $e["message"];
+ }
+ }
+
+
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel !== null) {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ $this->autoCommit = false;
+ return $this->connection;
+ }
+
+ public function commit()
+ {
+ if (oci_commit($this->connection)) {
+ $this->autoCommit = true;
+ } else {
+ $e = oci_error($this->connection);
+ throw new Sabel_Db_Exception_Driver($e["message"]);
+ }
+ }
+
+ public function rollback()
+ {
+ if (oci_rollback($this->connection)) {
+ $this->autoCommit = true;
+ } else {
+ $e = oci_error($this->connection);
+ throw new Sabel_Db_Exception_Driver($e["message"]);
+ }
+ }
+
+ public function close($connection)
+ {
+ oci_close($connection);
+ unset($this->connection);
+ }
+
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function setOffset($offset)
+ {
+ $this->offset = $offset;
+ }
+
+ public function setLastInsertId($id)
+ {
+ $this->lastInsertId = $id;
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $connection = $this->connection;
+ $sql = $this->bind($sql, $bindParams);
+
+ // array $blobs Sabel_Db_Oci_Blob[]
+ $blobs = (isset($additionalParameters["blob"])) ? $additionalParameters["blob"] : array();
+
+ if (empty($blobs)) {
+ $execMode = ($this->autoCommit) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT;
+ $ociStmt = oci_parse($connection, $sql);
+ $result = oci_execute($ociStmt, $execMode);
+ } else {
+ $ociStmt = oci_parse($connection, $sql);
+ foreach ($blobs as $column => $blob) {
+ $lob = $blob->getLob();
+ oci_bind_by_name($ociStmt, ":" . $column, $lob, -1, SQLT_BLOB);
+ }
+
+ $result = oci_execute($ociStmt, OCI_DEFAULT);
+ foreach ($blobs as $blob) $blob->save();
+ }
+
+ if (!$result) $this->executeError($ociStmt);
+
+ $rows = array();
+ if (oci_statement_type($ociStmt) === "SELECT") {
+ oci_fetch_all($ociStmt, $rows, $this->offset, $this->limit, OCI_ASSOC|OCI_FETCHSTATEMENT_BY_ROW);
+ $rows = array_map("array_change_key_case", $rows);
+ } else {
+ $this->affectedRows = oci_num_rows($ociStmt);
+ }
+
+ if (!empty($blobs) && $this->autoCommit) {
+ $this->commit();
+ }
+
+ oci_free_statement($ociStmt);
+ return (empty($rows)) ? null : $rows;
+ }
+
+ public function getLastInsertId()
+ {
+ return $this->lastInsertId;
+ }
+
+ public function setTransactionIsolationLevel($level)
+ {
+ switch ($level) {
+ case self::TRANS_READ_UNCOMMITTED:
+ case self::TRANS_READ_COMMITTED:
+ $query = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";
+ break;
+ case self::TRANS_REPEATABLE_READ:
+ case self::TRANS_SERIALIZABLE:
+ $query = "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE";
+ break;
+ default:
+ throw new Sabel_Exception_InvalidArgument("invalid isolation level.");
+ }
+
+ $this->execute($query);
+ }
+
+ private function executeError($ociStmt)
+ {
+ $e = oci_error($ociStmt);
+ throw new Sabel_Db_Exception_Driver($e["message"] . ", SQL:" . $e["sqltext"]);
+ }
+}
diff --git a/sabel/db/oci/Metadata.php b/sabel/db/oci/Metadata.php
new file mode 100755
index 0000000..0af1e3a
--- /dev/null
+++ b/sabel/db/oci/Metadata.php
@@ -0,0 +1,254 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Oci_Metadata extends Sabel_Db_Abstract_Metadata
+{
+ /**
+ * @var array
+ */
+ private $comments = array();
+
+ /**
+ * @var array
+ */
+ private $sequences = array();
+
+ /**
+ * @var array
+ */
+ private $primaryKeys = array();
+
+ public function getTableList()
+ {
+ $sql = "SELECT table_name FROM all_tables WHERE owner = '{$this->schemaName}'";
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $tables = array();
+ foreach ($rows as $row) {
+ $tables[] = $row["table_name"];
+ }
+
+ return array_map("strtolower", $tables);
+ }
+
+ protected function createColumns($tblName)
+ {
+ $tblName = strtoupper($tblName);
+
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $this->createSequences();
+ $this->createPrimaryKeys($tblName);
+ $this->createComments($tblName);
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $colName = strtolower($row["column_name"]);
+ $columns[$colName] = $this->createColumn($row);
+ }
+
+ return $columns;
+ }
+
+ protected function createColumn($row)
+ {
+ $column = new Sabel_Db_Metadata_Column();
+ $column->name = strtolower($row["column_name"]);
+ $column->nullable = ($row["nullable"] !== "N");
+
+ $type = strtolower($row["data_type"]);
+ $default = trim($row["data_default"]);
+ $precision = (int)$row["data_precision"];
+
+ if ($type === "number") {
+ if ($precision === 1 && ($default === "1" || $default === "0")) {
+ $column->type = Sabel_Db_Type::BOOL;
+ } else {
+ if ($precision === 5) {
+ $type = "smallint";
+ } elseif ($precision === 19) {
+ $type = "bigint";
+ } else {
+ $type = "integer";
+ }
+ }
+ }
+
+ if (!$column->isBool()) {
+ if ($type === "float") {
+ $type = ($precision === 24) ? "float" : "double";
+ } elseif ($type === "date" && !$this->isDate($column->name)) {
+ $type = "datetime";
+ }
+
+ Sabel_Db_Type_Manager::create()->applyType($column, $type);
+ }
+
+ $this->setDefault($column, $default);
+
+ $seq = $row["table_name"] . "_" . $row["column_name"] . "_seq";
+ $column->increment = (in_array(strtoupper($seq), $this->sequences));
+ $column->primary = (in_array($column->name, $this->primaryKeys));
+
+ if ($column->primary) {
+ $column->nullable = false;
+ }
+
+ if ($column->isString()) {
+ $column->max = (int)$row["char_length"];
+ }
+
+ return $column;
+ }
+
+ public function getForeignKeys($tblName)
+ {
+ $tblName = strtoupper($tblName);
+
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $column = strtolower($row["column_name"]);
+ $columns[$column]["referenced_table"] = strtolower($row["ref_table"]);
+ $columns[$column]["referenced_column"] = strtolower($row["ref_column"]);
+ $columns[$column]["on_delete"] = $row["delete_rule"];
+ $columns[$column]["on_update"] = "NO ACTION";
+ }
+
+ return $columns;
+ }
+
+ public function getUniques($tblName)
+ {
+ $tblName = strtoupper($tblName);
+
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $uniques = array();
+ foreach ($rows as $row) {
+ $key = $row["constraint_name"];
+ $uniques[$key][] = strtolower($row["column_name"]);
+ }
+
+ return array_values($uniques);
+ }
+
+ private function createSequences()
+ {
+ if (!empty($this->sequences)) return;
+
+ $sql = "SELECT sequence_name FROM all_sequences "
+ . "WHERE sequence_owner = '{$this->schemaName}'";
+
+ $seqs =& $this->sequences;
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return;
+
+ foreach ($rows as $row) {
+ $seqs[] = $row["sequence_name"];
+ }
+ }
+
+ private function createPrimaryKeys($tblName)
+ {
+ if (!empty($this->primaryKeys)) return;
+
+ $sql = <<primaryKeys;
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return;
+
+ foreach ($rows as $row) {
+ $keys[] = strtolower($row["column_name"]);
+ }
+ }
+
+ private function createComments($tblName)
+ {
+ $sql = "SELECT column_name, comments FROM all_col_comments "
+ . "WHERE owner = '{$this->schemaName}' AND table_name = '{$tblName}'";
+
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return;
+
+ foreach ($rows as $row) {
+ if (($comment = $row["comments"]) !== null) {
+ $colName = strtolower($row["column_name"]);
+ $this->comments[$colName] = $comment;
+ }
+ }
+ }
+
+ private function setDefault($co, $default)
+ {
+ $this->setDefaultValue($co, $default);
+ if (is_string($co->default) && ($length = strlen($co->default)) > 1) {
+ if ($co->default{0} === "'" && substr($co->default, --$length, 1) === "'") {
+ $co->default = substr($co->default, 1, --$length);
+ }
+ }
+ }
+
+ private function isDate($colName)
+ {
+ return (isset($this->comments[$colName]) && $this->comments[$colName] === "date");
+ }
+}
diff --git a/sabel/db/oci/Migration.php b/sabel/db/oci/Migration.php
new file mode 100755
index 0000000..71bce33
--- /dev/null
+++ b/sabel/db/oci/Migration.php
@@ -0,0 +1,232 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Oci_Migration extends Sabel_Db_Abstract_Migration
+{
+ protected $types = array(Sabel_Db_Type::INT => "NUMBER(10)",
+ Sabel_Db_Type::BIGINT => "NUMBER(19)",
+ Sabel_Db_Type::SMALLINT => "NUMBER(5)",
+ Sabel_Db_Type::FLOAT => "FLOAT(24)",
+ Sabel_Db_Type::DOUBLE => "FLOAT(53)",
+ Sabel_Db_Type::BOOL => "NUMBER(1)",
+ Sabel_Db_Type::STRING => "VARCHAR2",
+ Sabel_Db_Type::TEXT => "CLOB",
+ Sabel_Db_Type::DATETIME => "DATE",
+ Sabel_Db_Type::DATE => "DATE",
+ Sabel_Db_Type::BINARY => "BLOB");
+
+ protected function create()
+ {
+ $schema = $this->getSchema();
+ $tables = $schema->getTableList();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (in_array($this->tblName, $tables, true)) {
+ Sabel_Console::warning("table '{$this->tblName}' already exists. (SKIP)");
+ } else {
+ $this->createTable($this->filePath);
+ }
+ } elseif (in_array($this->tblName, $tables, true)) {
+ $this->dropSequence($schema->getTable($this->tblName)->getSequenceColumn());
+ $this->executeQuery("DROP TABLE " . $this->quoteIdentifier($this->tblName));
+ } else {
+ Sabel_Console::warning("unknown table '{$this->tblName}'. (SKIP)");
+ }
+ }
+
+ protected function createTable($filePath)
+ {
+ $create = $this->getReader($filePath)->readCreate();
+ $this->executeQuery($this->getCreateSql($create));
+
+ foreach ($create->getColumns() as $column) {
+ if ($column->increment) {
+ $seqName = strtoupper($this->tblName) . "_" . strtoupper($column->name) . "_SEQ";
+ $this->executeQuery("CREATE SEQUENCE " . $seqName);
+ } elseif ($column->isDate()) {
+ $tblName = $this->quoteIdentifier($this->tblName);
+ $colName = $this->quoteIdentifier($column->name);
+ $this->executeQuery("COMMENT ON COLUMN {$tblName}.{$colName} IS 'date'");
+ }
+ }
+ }
+
+ protected function getCreateSql($create)
+ {
+ $query = array();
+ $fkeys = $create->getForeignKeys();
+
+ foreach ($create->getColumns() as $column) {
+ $line = $this->createColumnAttributes($column);
+ if (isset($fkeys[$column->name])) {
+ $fkey = $fkeys[$column->name]->get();
+ $line .= " REFERENCES {$this->quoteIdentifier($fkey->refTable)}";
+ $line .= "({$this->quoteIdentifier($fkey->refColumn)})";
+
+ if ($fkey->onDelete !== null && $fkey->onDelete !== "NO ACTION") {
+ $line .= " ON DELETE " . $fkey->onDelete;
+ }
+ }
+
+ $query[] = $line;
+ }
+
+ if ($pkeys = $create->getPrimaryKeys()) {
+ $cols = $this->quoteIdentifier($pkeys);
+ $query[] = "PRIMARY KEY(" . implode(", ", $cols) . ")";
+ }
+
+ if ($uniques = $create->getUniques()) {
+ foreach ($uniques as $unique) {
+ $cols = $this->quoteIdentifier($unique);
+ $query[] = "UNIQUE (" . implode(", ", $cols) . ")";
+ }
+ }
+
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+ return "CREATE TABLE $quotedTblName (" . implode(", ", $query) . ")";
+ }
+
+ protected function drop()
+ {
+ $restore = $this->getRestoreFileName();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (is_file($restore)) unlink($restore);
+
+ $schema = $this->getSchema()->getTable($this->tblName);
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeTable($schema)->close();
+
+ $this->executeQuery("DROP TABLE " . $this->quoteIdentifier($this->tblName));
+ $this->dropSequence($schema->getSequenceColumn());
+ } else {
+ $this->createTable($restore);
+ }
+ }
+
+ private function dropSequence($incCol)
+ {
+ if ($incCol !== null) {
+ $seqName = strtoupper($this->tblName) . "_" . strtoupper($incCol) . "_SEQ";
+ $this->executeQuery("DROP SEQUENCE " . $seqName);
+ }
+ }
+
+ protected function changeColumnUpgrade($columns, $schema)
+ {
+ $this->_changeColumn($columns, $schema);
+ }
+
+ protected function changeColumnDowngrade($columns, $schema)
+ {
+ $this->_changeColumn($columns, $schema);
+ }
+
+ protected function createColumnAttributes($col)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($col->name);
+ $line[] = $this->getTypeDefinition($col);
+ $line[] = $this->getDefaultValue($col);
+
+ if (($nullable = $this->getNullableDefinition($col)) !== "") {
+ $line[] = $nullable;
+ }
+
+ return preg_replace("/[ ]{2,}/", " ", implode(" ", $line));
+ }
+
+ protected function alterChange($column, $current)
+ {
+ $line = array();
+ $line[] = $column->name;
+
+ if ($current->isText() && $column->type !== null && !$column->isText()) {
+ Sabel_Console::warning("cannot modify lob column '{$current->name}'. (SKIP)");
+ } elseif (!$current->isText()) {
+ $col = ($column->type === null) ? $current : $column;
+ $type = $this->getTypeDefinition($col, false);
+
+ if ($col->isString()) {
+ $max = ($column->max === null) ? $current->max : $column->max;
+ $line[] = $type . "({$max})";
+ } else {
+ $line[] = $type;
+ }
+ }
+
+ if (($d = $column->default) === _NULL) {
+ $line[] = "DEFAULT NULL";
+ } else {
+ $cd = $current->default;
+
+ if ($d === $cd) {
+ $line[] = $this->getDefaultValue($current);
+ } else {
+ $this->valueCheck($column, $d);
+ $line[] = $this->getDefaultValue($column);
+ }
+ }
+
+ if ($current->nullable === true && $column->nullable === false) {
+ $line[] = "NOT NULL";
+ } elseif ($current->nullable === false && $column->nullable === true) {
+ $line[] = "NULL";
+ }
+
+ return implode(" ", $line);
+ }
+
+ protected function getTypeDefinition($col, $withLength = true)
+ {
+ if ($col->isString() && $withLength) {
+ return $this->types[$col->type] . "({$col->max})";
+ } else {
+ return $this->types[$col->type];
+ }
+ }
+
+ protected function getNullableDefinition($column)
+ {
+ return ($column->nullable === false) ? "NOT NULL" : "";
+ }
+
+ protected function getBooleanAttr($value)
+ {
+ $v = ($value === true) ? "1" : "0";
+ return "DEFAULT " . $v;
+ }
+
+ private function _changeColumn($columns, $schema)
+ {
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+
+ foreach ($columns as $column) {
+ $current = $schema->getColumnByName($column->name);
+ $line = $this->alterChange($column, $current);
+ $this->executeQuery("ALTER TABLE $quotedTblName MODIFY $line");
+ }
+ }
+
+ private function valueCheck($column, $default)
+ {
+ if ($default === null) return true;
+
+ if (($column->isBool() && !is_bool($default)) ||
+ ($column->isNumeric() && !is_numeric($default))) {
+ $message = __METHOD__ . "() invalid default value for '{$column->name}'.";
+ throw new Sabel_Db_Exception($message);
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/sabel/db/oci/Statement.php b/sabel/db/oci/Statement.php
new file mode 100755
index 0000000..08b0436
--- /dev/null
+++ b/sabel/db/oci/Statement.php
@@ -0,0 +1,164 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Oci_Statement extends Sabel_Db_Statement
+{
+ protected $blobs = array();
+
+ public function __construct(Sabel_Db_Oci_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function clear()
+ {
+ $this->blobs = array();
+ return parent::clear();
+ }
+
+ public function values(array $values)
+ {
+ $columns = $this->metadata->getColumns();
+
+ foreach ($values as $k => &$v) {
+ if (isset($columns[$k]) && $columns[$k]->isBinary()) {
+ $this->blobs[$k] = $this->createBlob($v);
+ $v = new Sabel_Db_Statement_Expression($this, "EMPTY_BLOB()");
+ }
+ }
+
+ $this->values = $values;
+ $this->binds($values);
+
+ return $this;
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ $query = $this->getQuery();
+ $blobs = $this->blobs;
+
+ if (!empty($blobs)) {
+ $cols = array();
+ $hlds = array();
+ foreach (array_keys($blobs) as $column) {
+ $cols[] = $column;
+ $hlds[] = ":" . $column;
+ }
+
+ $query .= " RETURNING " . implode(", ", $cols) . " INTO " . implode(", ", $hlds);
+ }
+
+ $additionalParameters["blob"] = $blobs;
+ return parent::execute($bindValues, $additionalParameters, $query);
+ }
+
+ public function escape(array $values)
+ {
+ if (extension_loaded("mbstring")) {
+ $currentRegexEnc = mb_regex_encoding();
+ mb_regex_encoding(mb_internal_encoding());
+
+ foreach ($values as $k => &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? 1 : 0;
+ } elseif (is_string($val)) {
+ $val = "'" . mb_ereg_replace("'", "''", $val) . "'";
+ }
+ }
+
+ mb_regex_encoding($currentRegexEnc);
+ } else {
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? 1 : 0;
+ } elseif (is_string($val)) {
+ $val = "'" . str_replace("'", "''", $val) . "'";
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ public function createBlob($binary)
+ {
+ $conn = $this->driver->getConnection();
+ return new Sabel_Db_Oci_Blob($conn, $binary);
+ }
+
+ public function createInsertSql()
+ {
+ if (($column = $this->seqColumn) !== null) {
+ $seqName = strtoupper("{$this->table}_{$column}_seq");
+ $rows = $this->driver->execute("SELECT {$seqName}.NEXTVAL AS id FROM DUAL");
+ $this->values[$column] = $id = $rows[0]["id"];
+ $this->bind($column, $id);
+ $this->driver->setLastInsertId($id);
+ }
+
+ return parent::createInsertSql();
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '"' . strtoupper($v) . '"';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '"' . strtoupper($arg) . '"';
+ } else {
+ $message = "argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ protected function createConstraintSql()
+ {
+ $sql = "";
+ $c = $this->constraints;
+
+ if (isset($c["order"])) {
+ $sql .= " ORDER BY " . $this->quoteIdentifierForOrderBy($c["order"]);
+ }
+
+ $limit = (isset($c["limit"])) ? $c["limit"] : null;
+ $offset = (isset($c["offset"])) ? $c["offset"] : null;
+
+ $this->driver->setLimit($limit);
+ $this->driver->setOffset($offset);
+
+ return $sql;
+ }
+
+ protected function quoteIdentifierForOrderBy($orders)
+ {
+ $results = array();
+ foreach ($orders as $column => $order) {
+ $mode = strtoupper($order["mode"]);
+ $nulls = strtoupper($order["nulls"]);
+
+ if (($pos = strpos($column, ".")) !== false) {
+ $tblName = convert_to_tablename(substr($column, 0, $pos));
+ $column = $this->quoteIdentifier($tblName) . "."
+ . $this->quoteIdentifier(substr($column, $pos + 1));
+ } else {
+ $column = $this->quoteIdentifier($column);
+ }
+
+ $results[] = "{$column} {$mode} NULLS {$nulls}";
+ }
+
+ return implode(", ", $results);
+ }
+}
diff --git a/sabel/db/pdo/Blob.php b/sabel/db/pdo/Blob.php
new file mode 100755
index 0000000..53eba0e
--- /dev/null
+++ b/sabel/db/pdo/Blob.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Blob extends Sabel_Db_Abstract_Blob
+{
+ public function __construct($binary)
+ {
+ $this->binary = $binary;
+ }
+
+ public function getData()
+ {
+ return $this->binary;
+ }
+}
diff --git a/sabel/db/pdo/Driver.php b/sabel/db/pdo/Driver.php
new file mode 100755
index 0000000..2d5703b
--- /dev/null
+++ b/sabel/db/pdo/Driver.php
@@ -0,0 +1,112 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Pdo_Driver extends Sabel_Db_Driver
+{
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel !== null) {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ try {
+ $this->connection->beginTransaction();
+ $this->autoCommit = false;
+ return $this->connection;
+ } catch (PDOException $e) {
+ throw new Sabel_Db_Exception_Driver($e->getMessage());
+ }
+ }
+
+ public function commit()
+ {
+ try {
+ $this->connection->commit();
+ $this->autoCommit = true;
+ } catch (PDOException $e) {
+ throw new Sabel_Db_Exception_Driver($e->getMessage());
+ }
+ }
+
+ public function rollback()
+ {
+ try {
+ $this->connection->rollback();
+ $this->autoCommit = true;
+ } catch (PDOException $e) {
+ throw new Sabel_Db_Exception_Driver($e->getMessage());
+ }
+ }
+
+ public function close($connection)
+ {
+ unset($connection);
+ unset($this->connection);
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $connection = $this->connection;
+ if (!($pdoStmt = $connection->prepare($sql))) {
+ $error = $connection->errorInfo();
+ throw new Sabel_Db_Exception_Driver("PdoStatement is invalid. {$error[2]}");
+ }
+
+ $hasBlob = false;
+ foreach ($bindParams as $name => $value) {
+ if ($value instanceof Sabel_Db_Pdo_Blob) {
+ $hasBlob = true;
+ $pdoStmt->bindValue($name, $value->getData(), PDO::PARAM_LOB);
+ } else {
+ $pdoStmt->bindValue($name, $value);
+ }
+ }
+
+ if ($hasBlob && $this->autoCommit) {
+ $connection->beginTransaction();
+ }
+
+ if (!$result = $pdoStmt->execute()) {
+ $this->executeError($connection, $pdoStmt, $bindParams);
+ }
+
+ $rows = $pdoStmt->fetchAll(PDO::FETCH_ASSOC);
+ $this->affectedRows = $pdoStmt->rowCount();
+ $pdoStmt->closeCursor();
+
+ if ($hasBlob && $this->autoCommit) {
+ $connection->commit();
+ }
+
+ return (empty($rows)) ? null : $rows;
+ }
+
+ private function executeError($conn, $pdoStmt, $bindParams)
+ {
+ if (is_object($pdoStmt)) {
+ $error = $pdoStmt->errorInfo();
+ $sql = $pdoStmt->queryString;
+ } else {
+ $error = $conn->errorInfo();
+ $sql = null;
+ }
+
+ $error = (isset($error[2])) ? $error[2] : print_r($error, true);
+ if ($sql !== null) $error .= ", SQL: $sql";
+
+ if (!empty($bindParams)) {
+ $error .= PHP_EOL . "BIND_PARAMS: " . print_r($bindParams, true);
+ }
+
+ throw new Sabel_Db_Exception_Driver($error);
+ }
+}
diff --git a/sabel/db/pdo/Statement.php b/sabel/db/pdo/Statement.php
new file mode 100755
index 0000000..54d31f0
--- /dev/null
+++ b/sabel/db/pdo/Statement.php
@@ -0,0 +1,68 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Db_Pdo_Statement extends Sabel_Db_Statement
+{
+ abstract public function escape(array $values);
+
+ public function __construct(Sabel_Db_Pdo_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function bind($key, $val)
+ {
+ $this->bindValues[":{$key}"] = $val;
+
+ return $this;
+ }
+
+ public function unbind($key)
+ {
+ if (is_array($key)) {
+ foreach ($key as $k) {
+ unset($this->bindValues[":{$k}"]);
+ }
+ } else {
+ unset($this->bindValues[":{$key}"]);
+ }
+
+ return $this;
+ }
+
+ public function values(array $values)
+ {
+ $columns = $this->metadata->getColumns();
+
+ foreach ($values as $k => &$v) {
+ if (isset($columns[$k]) && $columns[$k]->isBinary()) {
+ $v = $this->createBlob($v);
+ }
+ }
+
+ $this->values = $values;
+ $this->binds($values);
+
+ return $this;
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ $query = preg_replace('/@([a-zA-Z0-9_]+)@/', ':$1', $this->getQuery());
+ return parent::execute($bindValues, $additionalParameters, $query);
+ }
+
+ public function createBlob($binary)
+ {
+ return new Sabel_Db_Pdo_Blob($binary);
+ }
+}
diff --git a/sabel/db/pdo/mysql/Driver.php b/sabel/db/pdo/mysql/Driver.php
new file mode 100755
index 0000000..b744661
--- /dev/null
+++ b/sabel/db/pdo/mysql/Driver.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Mysql_Driver extends Sabel_Db_Pdo_Driver
+{
+ public function connect(array $params)
+ {
+ try {
+ $dsn = "mysql:host={$params["host"]};dbname={$params["database"]}";
+ if (isset($params["port"])) $dsn .= ";port={$params["port"]}";
+ $conn = new PDO($dsn, $params["user"], $params["password"]);
+
+ if (isset($params["charset"])) {
+ $conn->exec("SET NAMES " . $params["charset"]);
+ }
+
+ return $conn;
+ } catch (PDOException $e) {
+ return $e->getMessage();
+ }
+ }
+
+ public function getLastInsertId()
+ {
+ $rows = $this->execute("SELECT LAST_INSERT_ID() as `id`");
+ return (isset($rows[0]["id"])) ? $rows[0]["id"] : 0;
+ }
+}
diff --git a/sabel/db/pdo/mysql/Statement.php b/sabel/db/pdo/mysql/Statement.php
new file mode 100755
index 0000000..d08b2da
--- /dev/null
+++ b/sabel/db/pdo/mysql/Statement.php
@@ -0,0 +1,57 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Mysql_Statement extends Sabel_Db_Pdo_Statement
+{
+ public function values(array $values)
+ {
+ if ($this->isInsert()) {
+ foreach ($this->metadata->getColumns() as $colName => $column) {
+ if (!isset($values[$colName]) && $this->isVarcharOfDefaultNull($column)) {
+ $values[$colName] = null;
+ }
+ }
+ }
+
+ return parent::values($values);
+ }
+
+ public function escape(array $values)
+ {
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? 1 : 0;
+ }
+ }
+
+ return $values;
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '`' . $v . '`';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '`' . $arg . '`';
+ } else {
+ $message = "argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ private function isVarcharOfDefaultNull($column)
+ {
+ return ($column->isString() && $column->default === null);
+ }
+}
diff --git a/sabel/db/pdo/oci/Blob.php b/sabel/db/pdo/oci/Blob.php
new file mode 100755
index 0000000..7746e61
--- /dev/null
+++ b/sabel/db/pdo/oci/Blob.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Oci_Blob extends Sabel_Db_Pdo_Blob
+{
+ protected $filePath = "";
+
+ public function __construct($binary)
+ {
+ $this->binary = $binary;
+ $this->filePath = get_temp_dir() . DS . "sbl_" . md5hash();
+ }
+
+ public function getData()
+ {
+ file_put_contents($this->filePath, $this->binary);
+ return fopen($this->filePath, "rb");
+ }
+
+ public function unlink()
+ {
+ if (is_file($this->filePath)) {
+ unlink($this->filePath);
+ }
+ }
+}
diff --git a/sabel/db/pdo/oci/Driver.php b/sabel/db/pdo/oci/Driver.php
new file mode 100755
index 0000000..9c39793
--- /dev/null
+++ b/sabel/db/pdo/oci/Driver.php
@@ -0,0 +1,70 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Oci_Driver extends Sabel_Db_Pdo_Driver
+{
+ private $lastInsertId = null;
+
+ public function connect(array $params)
+ {
+ try {
+ $dsn = "oci:dbname=//{$params["host"]}";
+ if (isset($params["port"])) $dsn .= ":port={$params["port"]}";
+ $dsn .= "/" . $params["database"];
+ if (isset($params["charset"])) $dsn .= ";charset={$params["charset"]}";
+
+ $conn = new PDO($dsn, $params["user"], $params["password"]);
+ $pdoStmt = $conn->prepare("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'");
+ $pdoStmt->execute();
+
+ return $conn;
+ } catch (PDOException $e) {
+ return $e->getMessage();
+ }
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ if (($result = parent::execute($sql, $bindParams)) === null) {
+ return null;
+ } else {
+ return array_map("array_change_key_case", $result);
+ }
+ }
+
+ public function setLastInsertId($id)
+ {
+ $this->lastInsertId = $id;
+ }
+
+ public function getLastInsertId()
+ {
+ return $this->lastInsertId;
+ }
+
+ public function setTransactionIsolationLevel($level)
+ {
+ switch ($level) {
+ case self::TRANS_READ_UNCOMMITTED:
+ case self::TRANS_READ_COMMITTED:
+ $query = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";
+ break;
+ case self::TRANS_REPEATABLE_READ:
+ case self::TRANS_SERIALIZABLE:
+ $query = "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE";
+ break;
+ default:
+ throw new Sabel_Exception_InvalidArgument("invalid isolation level.");
+ }
+
+ $this->execute($query);
+ }
+}
diff --git a/sabel/db/pdo/oci/Statement.php b/sabel/db/pdo/oci/Statement.php
new file mode 100755
index 0000000..57db203
--- /dev/null
+++ b/sabel/db/pdo/oci/Statement.php
@@ -0,0 +1,197 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Oci_Statement extends Sabel_Db_Pdo_Statement
+{
+ protected $blobs = array();
+
+ public function values(array $values)
+ {
+ $columns = $this->metadata->getColumns();
+ foreach ($values as $k => &$v) {
+ if (isset($columns[$k]) && $columns[$k]->isBinary()) {
+ $this->blobs[$k] = $this->createBlob($v);
+ $v = new Sabel_Db_Statement_Expression($this, "EMPTY_BLOB()");
+ }
+ }
+
+ $this->values = $values;
+ $this->binds($values);
+
+ return $this;
+ }
+
+ public function clear()
+ {
+ $this->blobs = array();
+ return parent::clear();
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ $query = $this->getQuery();
+ $blobs = $this->blobs;
+
+ if (!empty($blobs) && ($this->isInsert() || $this->isUpdate())) {
+ $cols = array();
+ $hlds = array();
+ foreach ($blobs as $column => $blob) {
+ $cols[] = $column;
+ $hlds[] = ":" . $column;
+ $this->bindValues[$column] = $blob;
+ }
+
+ $query .= " RETURNING " . implode(", ", $cols) . " INTO " . implode(", ", $hlds);
+ }
+
+ $this->query = $query;
+ $result = parent::execute($bindValues, $additionalParameters);
+
+ if (!empty($blobs) && ($this->isInsert() || $this->isUpdate())) {
+ foreach ($blobs as $blob) $blob->unlink();
+ }
+
+ if (!$this->isSelect() || empty($result)) return $result;
+
+ // FETCH LOB CONTENTS
+
+ $lobColumns = array();
+ foreach ($this->metadata->getColumns() as $column) {
+ if ($column->isText() || $column->isBinary()) {
+ $lobColumns[] = $column->name;
+ }
+ }
+
+ if (!empty($lobColumns)) {
+ foreach ($result as &$row) {
+ foreach ($lobColumns as $colName) {
+ if (isset($row[$colName])) {
+ $row[$colName] = stream_get_contents($row[$colName]);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function escape(array $values)
+ {
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? 1 : 0;
+ }
+ }
+
+ return $values;
+ }
+
+ public function createBlob($binary)
+ {
+ return new Sabel_Db_Pdo_Oci_Blob($binary);
+ }
+
+ public function quoteIdentifier($arg)
+ {
+ if (is_array($arg)) {
+ foreach ($arg as &$v) {
+ $v = '"' . strtoupper($v) . '"';
+ }
+ return $arg;
+ } elseif (is_string($arg)) {
+ return '"' . strtoupper($arg) . '"';
+ } else {
+ $message = "argument must be a string or an array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ public function createInsertSql()
+ {
+ if (($column = $this->seqColumn) !== null) {
+ $seqName = strtoupper("{$this->table}_{$column}_seq");
+ $rows = $this->driver->execute("SELECT {$seqName}.NEXTVAL AS id FROM DUAL");
+ $this->values[$column] = $id = $rows[0]["id"];
+ $this->bind($column, $id);
+ $this->driver->setLastInsertId($id);
+ }
+
+ return parent::createInsertSql();
+ }
+
+ protected function createSelectSql()
+ {
+ $tblName = $this->quoteIdentifier($this->table);
+ $projection = $this->getProjection();
+
+ $c = $this->constraints;
+ $limit = null;
+ $offset = null;
+
+ if (isset($c["offset"]) && isset($c["limit"])) {
+ $limit = $c["limit"];
+ $offset = $c["offset"];
+ } elseif (isset($c["offset"]) && !isset($c["limit"])) {
+ $limit = 100;
+ $offset = $c["offset"];
+ } elseif (isset($c["limit"]) && !isset($c["offset"])) {
+ $limit = $c["limit"];
+ $offset = 0;
+ }
+
+ if ($limit !== null) {
+ if (isset($c["order"])) {
+ $order = $c["order"];
+ } else {
+ $order = convert_to_modelname($this->metadata->getTableName()) . "."
+ . $this->metadata->getPrimaryKey() . " ASC";
+ }
+
+ $orderBy = " ORDER BY " . $this->quoteIdentifierForOrderBy($order);
+ $sql = "SELECT * FROM (SELECT ROW_NUMBER() OVER({$orderBy}) \"SDB_RN\", $projection "
+ . "FROM $tblName" . $this->join . $this->where . $orderBy . ") "
+ . "WHERE \"SDB_RN\" BETWEEN " . ($offset + 1) . " AND " . ($offset + $limit);
+ } else {
+ $sql = "SELECT $projection FROM $tblName" . $this->join . $this->where;
+
+ if (isset($c["order"])) {
+ $sql .= " ORDER BY " . $this->quoteIdentifierForOrderBy($c["order"]);
+ }
+ }
+
+ if ($this->forUpdate) {
+ $sql .= " FOR UPDATE";
+ }
+
+ return $sql;
+ }
+
+ protected function quoteIdentifierForOrderBy($orders)
+ {
+ $results = array();
+ foreach ($orders as $column => $order) {
+ $mode = strtoupper($order["mode"]);
+ $nulls = strtoupper($order["nulls"]);
+
+ if (($pos = strpos($column, ".")) !== false) {
+ $tblName = convert_to_tablename(substr($column, 0, $pos));
+ $column = $this->quoteIdentifier($tblName) . "."
+ . $this->quoteIdentifier(substr($column, $pos + 1));
+ } else {
+ $column = $this->quoteIdentifier($column);
+ }
+
+ $results[] = "{$column} {$mode} NULLS {$nulls}";
+ }
+
+ return implode(", ", $results);
+ }
+}
diff --git a/sabel/db/pdo/pgsql/Driver.php b/sabel/db/pdo/pgsql/Driver.php
new file mode 100755
index 0000000..a6b56a0
--- /dev/null
+++ b/sabel/db/pdo/pgsql/Driver.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Pgsql_Driver extends Sabel_Db_Pdo_Driver
+{
+ public function connect(array $params)
+ {
+ try {
+ $dsn = "pgsql:host={$params["host"]};dbname={$params["database"]}";
+ if (isset($params["port"])) $dsn .= ";port={$params["port"]}";
+ $conn = new PDO($dsn, $params["user"], $params["password"]);
+
+ if (isset($params["charset"])) {
+ $conn->exec("SET NAMES " . $params["charset"]);
+ }
+
+ return $conn;
+ } catch (PDOException $e) {
+ return $e->getMessage();
+ }
+ }
+
+ public function getLastInsertId()
+ {
+ $rows = $this->execute("SELECT LASTVAL() AS id");
+ return $rows[0]["id"];
+ }
+}
diff --git a/sabel/db/pdo/pgsql/Statement.php b/sabel/db/pdo/pgsql/Statement.php
new file mode 100755
index 0000000..008a6b9
--- /dev/null
+++ b/sabel/db/pdo/pgsql/Statement.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Pgsql_Statement extends Sabel_Db_Pdo_Statement
+{
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ $result = parent::execute($bindValues, $additionalParameters);
+ if (!$this->isSelect() || empty($result)) return $result;
+
+ // PDO_PGSQL BYTEA HACK
+
+ $binColumns = array();
+ foreach ($this->metadata->getColumns() as $column) {
+ if ($column->isBinary()) $binColumns[] = $column->name;
+ }
+
+ if (!empty($binColumns)) {
+ foreach ($result as &$row) {
+ foreach ($binColumns as $colName) {
+ if (isset($row[$colName])) {
+ $row[$colName] = stream_get_contents($row[$colName]);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function escape(array $values)
+ {
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? "t" : "f";
+ }
+ }
+
+ return $values;
+ }
+}
diff --git a/sabel/db/pdo/sqlite/Driver.php b/sabel/db/pdo/sqlite/Driver.php
new file mode 100755
index 0000000..d824d1e
--- /dev/null
+++ b/sabel/db/pdo/sqlite/Driver.php
@@ -0,0 +1,43 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Sqlite_Driver extends Sabel_Db_Pdo_Driver
+{
+ public function connect(array $params)
+ {
+ try {
+ return new PDO("sqlite:" . $params["database"]);
+ } catch (PDOException $e) {
+ return $e->getMessage();
+ }
+ }
+
+ public function begin($isolationLevel = null)
+ {
+ try {
+ $this->connection->beginTransaction();
+ return $this->connection;
+ } catch (PDOException $e) {
+ $message = $e->getMessage();
+ throw new Sabel_Db_Exception_Driver("pdo driver begin failed. {$message}");
+ }
+ }
+
+ public function getLastInsertId()
+ {
+ return $this->connection->lastInsertId();
+ }
+
+ public function setTransactionIsolationLevel($level)
+ {
+ // ignore
+ }
+}
diff --git a/sabel/db/pdo/sqlite/Metadata.php b/sabel/db/pdo/sqlite/Metadata.php
new file mode 100755
index 0000000..1e5e0b3
--- /dev/null
+++ b/sabel/db/pdo/sqlite/Metadata.php
@@ -0,0 +1,112 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Sqlite_Metadata extends Sabel_Db_Abstract_Metadata
+{
+ public function getTableList()
+ {
+ $sql = "SELECT name FROM sqlite_master WHERE type = 'table'";
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $tables = array();
+ foreach ($rows as $row) $tables[] = $row["name"];
+ return $tables;
+ }
+
+ public function getForeignKeys($tblName)
+ {
+ return null;
+ }
+
+ public function getUniques($tblName)
+ {
+ $createSql = $this->getCreateSql($tblName);
+ preg_match_all("/UNIQUE ?(\(([^)]+)\))/i", $createSql, $matches);
+ if (empty($matches[1])) return null;
+
+ $uniques = array();
+ foreach ($matches[2] as $unique) {
+ $unique = str_replace(array('"'), "", $unique);
+ $exp = array_map("trim", explode(",", $unique));
+ $uniques[] = $exp;
+ }
+
+ return $uniques;
+ }
+
+ protected function createColumns($tblName)
+ {
+ $rows = $this->driver->execute("PRAGMA table_info('{$tblName}')");
+ if (!$rows) return array();
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $co = new Sabel_Db_Metadata_Column();
+ $co->name = $row["name"];
+
+ if ($row["pk"] === "1") {
+ $co->primary = true;
+ $co->nullable = false;
+ $co->increment = ($row["type"] === "integer");
+ } else {
+ $co->primary = false;
+ $co->nullable = ($row["notnull"] === "0");
+ $co->increment = false;
+ }
+
+ if ($this->isBoolean($row["type"])) {
+ $co->type = Sabel_Db_Type::BOOL;
+ } elseif (!$this->isString($co, $row["type"])) {
+ Sabel_Db_Type_Manager::create()->applyType($co, $row["type"]);
+ }
+
+ $this->setDefaultValue($co, $row["dflt_value"]);
+ if (is_string($co->default) && ($length = strlen($co->default)) > 1) {
+ if ($co->default{0} === "'" && substr($co->default, --$length, 1) === "'") {
+ $co->default = substr($co->default, 1, --$length);
+ }
+ }
+
+ $columns[$co->name] = $co;
+ }
+
+ return $columns;
+ }
+
+ protected function isBoolean($type)
+ {
+ return ($type === "boolean" || $type === "bool");
+ }
+
+ protected function isString($co, $type)
+ {
+ $types = array("varchar", "char", "character");
+
+ foreach ($types as $sType) {
+ if (strpos($type, $sType) !== false) {
+ $length = strpbrk($type, "(");
+ $co->type = Sabel_Db_Type::STRING;
+ $co->max = ($length === false) ? 255 : (int)substr($length, 1, -1);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function getCreateSql($tblName)
+ {
+ $sql = "SELECT sql FROM sqlite_master WHERE name = '{$tblName}'";
+ $rows = $this->driver->execute($sql);
+ return $rows[0]["sql"];
+ }
+}
diff --git a/sabel/db/pdo/sqlite/Migration.php b/sabel/db/pdo/sqlite/Migration.php
new file mode 100755
index 0000000..9d67d5b
--- /dev/null
+++ b/sabel/db/pdo/sqlite/Migration.php
@@ -0,0 +1,210 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Sqlite_Migration extends Sabel_Db_Abstract_Migration
+{
+ protected $types = array(Sabel_Db_Type::INT => "int",
+ Sabel_Db_Type::BIGINT => "bigint",
+ Sabel_Db_Type::SMALLINT => "smallint",
+ Sabel_Db_Type::FLOAT => "float",
+ Sabel_Db_Type::DOUBLE => "double",
+ Sabel_Db_Type::BOOL => "boolean",
+ Sabel_Db_Type::STRING => "varchar",
+ Sabel_Db_Type::TEXT => "text",
+ Sabel_Db_Type::DATETIME => "datetime",
+ Sabel_Db_Type::DATE => "date",
+ Sabel_Db_Type::BINARY => "binary");
+
+ protected function createTable($filePath)
+ {
+ $create = $this->getReader($filePath)->readCreate();
+ $columns = $create->getColumns();
+ $pkeys = $create->getPrimaryKeys();
+ $uniques = $create->getUniques();
+ $query = $this->makeCreateSql($columns, $pkeys, $uniques);
+
+ $this->executeQuery($query);
+ }
+
+ protected function addColumn()
+ {
+ $columns = $this->getReader()->readAddColumn()->getColumns();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ $this->execAddColumn($columns);
+ } else {
+ $schema = $this->getSchema()->getTable($this->tblName);
+ $currents = $schema->getColumns();
+
+ foreach ($columns as $column) {
+ $name = $column->name;
+ if (isset($currents[$name])) unset($currents[$name]);
+ }
+
+ $this->dropColumnsAndRemakeTable($currents, $schema);
+ }
+ }
+
+ protected function dropColumn()
+ {
+ $restore = $this->getRestoreFileName();
+
+ if (Sabel_Db_Migration_Manager::isUpgrade()) {
+ if (is_file($restore)) unlink($restore);
+
+ $columns = $this->getReader()->readDropColumn()->getColumns();
+ $schema = $this->getSchema()->getTable($this->tblName);
+ $sColumns = $schema->getColumns();
+
+ $writer = new Sabel_Db_Migration_Writer($restore);
+ $writer->writeColumns($schema, $columns);
+ $writer->close();
+
+ foreach ($columns as $column) {
+ if (isset($sColumns[$column])) {
+ unset($sColumns[$column]);
+ } else {
+ $warning = "column '{$column}' does not exist. (SKIP)";
+ Sabel_Console::warning($warning);
+ }
+ }
+
+ $this->dropColumnsAndRemakeTable($sColumns, $schema);
+ } else {
+ $columns = $this->getReader($restore)->readAddColumn()->getColumns();
+ $this->execAddColumn($columns);
+ }
+ }
+
+ protected function changeColumnUpgrade($columns, $schema)
+ {
+ $sColumns = $schema->getColumns();
+
+ foreach ($columns as $column) {
+ if (isset($sColumns[$column->name])) {
+ $column = $this->alterChange($column, $sColumns[$column->name]);
+ $sColumns[$column->name] = $column;
+ }
+ }
+
+ $this->dropColumnsAndRemakeTable($sColumns, $schema);
+ }
+
+ protected function changeColumnDowngrade($columns, $schema)
+ {
+ $sColumns = $schema->getColumns();
+
+ foreach ($columns as $column) {
+ if (isset($sColumns[$column->name])) $sColumns[$column->name] = $column;
+ }
+
+ $this->dropColumnsAndRemakeTable($sColumns, $schema);
+ }
+
+ protected function createColumnAttributes($col)
+ {
+ $line = array($this->quoteIdentifier($col->name));
+
+ if ($col->increment) {
+ $line[] = "integer PRIMARY KEY";
+ } elseif ($col->isString()) {
+ $line[] = $this->types[$col->type] . "({$col->max})";
+ } else {
+ $line[] = $this->types[$col->type];
+ }
+
+ if ($col->nullable === false) $line[] = "NOT NULL";
+ $line[] = $this->getDefaultValue($col);
+
+ return implode(" ", $line);
+ }
+
+ private function dropColumnsAndRemakeTable($columns, $schema)
+ {
+ $stmt = $this->getStatement();
+ $pkeys = $schema->getPrimaryKey();
+ $uniques = $schema->getUniques();
+ $query = $this->makeCreateSql($columns, $pkeys, $uniques);
+
+ $quotedTblName = $stmt->quoteIdentifier($this->tblName);
+ $tmpTblName = "sbl_tmp_{$this->tblName}";
+ $query = str_replace(" TABLE $quotedTblName", " TABLE $tmpTblName", $query);
+
+ $stmt->getDriver()->begin();
+ $stmt->setQuery($query)->execute();
+
+ $projection = array();
+ foreach (array_keys($columns) as $key) $projection[] = $key;
+
+ $projection = implode(", ", $stmt->quoteIdentifier($projection));
+ $query = "INSERT INTO $tmpTblName SELECT $projection FROM $quotedTblName";
+
+ $stmt->setQuery($query)->execute();
+ $stmt->setQuery("DROP TABLE $quotedTblName")->execute();
+ $stmt->setQuery("ALTER TABLE $tmpTblName RENAME TO $quotedTblName")->execute();
+ $stmt->getDriver()->commit();
+ }
+
+ private function alterChange($column, $current)
+ {
+ if ($column->type === null) {
+ $column->type = $current->type;
+ }
+
+ if ($column->isString() && $column->max === null) {
+ $column->max = $current->max;
+ }
+
+ if ($column->nullable === null) {
+ $column->nullable = $current->nullable;
+ }
+
+ if ($column->default === _NULL) {
+ $column->default = null;
+ } elseif ($column->default === null) {
+ $column->default = $current->default;
+ }
+
+ return $column;
+ }
+
+ private function makeCreateSql($columns, $pkeys, $uniques)
+ {
+ $query = array();
+ $hasSeq = false;
+
+ foreach ($columns as $column) {
+ if ($column->increment) $hasSeq = true;
+ $query[] = $this->createColumnAttributes($column);
+ }
+
+ if ($pkeys && !$hasSeq) {
+ $cols = $this->quoteIdentifier($pkeys);
+ $query[] = "PRIMARY KEY(" . implode(", ", $cols) . ")";
+ }
+
+ if ($uniques) {
+ foreach ($uniques as $unique) {
+ $cols = $this->quoteIdentifier($unique);
+ $query[] = "UNIQUE (" . implode(", ", $cols) . ")";
+ }
+ }
+
+ $quotedTblName = $this->quoteIdentifier($this->tblName);
+ return "CREATE TABLE $quotedTblName (" . implode(", ", $query) . ")";
+ }
+
+ protected function getBooleanAttr($value)
+ {
+ $v = ($value === true) ? "true" : "false";
+ return "DEFAULT " . $v;
+ }
+}
diff --git a/sabel/db/pdo/sqlite/Statement.php b/sabel/db/pdo/sqlite/Statement.php
new file mode 100755
index 0000000..a06f4b9
--- /dev/null
+++ b/sabel/db/pdo/sqlite/Statement.php
@@ -0,0 +1,41 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pdo_Sqlite_Statement extends Sabel_Db_Pdo_Statement
+{
+ public function escape(array $values)
+ {
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? "true" : "false";
+ }
+ }
+
+ return $values;
+ }
+
+ protected function createSelectSql()
+ {
+ $tblName = $this->quoteIdentifier($this->table);
+ $projection = $this->getProjection();
+
+ $sql = "SELECT {$projection} FROM "
+ . $this->quoteIdentifier($this->table)
+ . $this->join
+ . $this->where
+ . $this->createConstraintSql();
+
+ // Not yet implemented
+ // if ($this->forUpdate) $sql .= " FOR UPDATE";
+
+ return $sql;
+ }
+}
diff --git a/sabel/db/pgsql/Blob.php b/sabel/db/pgsql/Blob.php
new file mode 100755
index 0000000..47d39dc
--- /dev/null
+++ b/sabel/db/pgsql/Blob.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pgsql_Blob extends Sabel_Db_Abstract_Blob
+{
+ protected $conn = null;
+
+ public function __construct($conn, $binary)
+ {
+ $this->conn = $conn;
+ $this->binary = $binary;
+ }
+
+ public function getData()
+ {
+ return "'" . pg_escape_bytea($this->conn, $this->binary) . "'";
+ }
+}
diff --git a/sabel/db/pgsql/Driver.php b/sabel/db/pgsql/Driver.php
new file mode 100755
index 0000000..350620b
--- /dev/null
+++ b/sabel/db/pgsql/Driver.php
@@ -0,0 +1,90 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pgsql_Driver extends Sabel_Db_Driver
+{
+ public function connect(array $params)
+ {
+ $host = $params["host"];
+ $user = $params["user"];
+ $pass = $params["password"];
+ $dbs = $params["database"];
+
+ $host = (isset($params["port"])) ? $host . " port=" . $params["port"] : $host;
+ $conn = pg_connect("host={$host} dbname={$dbs} user={$user} password={$pass}",
+ PGSQL_CONNECT_FORCE_NEW);
+
+ if ($conn) {
+ if (isset($params["charset"])) {
+ pg_set_client_encoding($conn, $params["charset"]);
+ }
+
+ return $conn;
+ } else {
+ return "cannot connect to PostgreSQL. please check your configuration.";
+ }
+ }
+
+ public function begin($isolationLevel = null)
+ {
+ if ($isolationLevel !== null) {
+ $this->setTransactionIsolationLevel($isolationLevel);
+ }
+
+ $this->execute("START TRANSACTION");
+ $this->autoCommit = false;
+ return $this->connection;
+ }
+
+ public function commit()
+ {
+ $this->execute("COMMIT");
+ $this->autoCommit = true;
+ }
+
+ public function rollback()
+ {
+ $this->execute("ROLLBACK");
+ $this->autoCommit = true;
+ }
+
+ public function close($connection)
+ {
+ pg_close($connection);
+ unset($this->connection);
+ }
+
+ public function execute($sql, $bindParams = array(), $additionalParameters = array())
+ {
+ $sql = $this->bind($sql, $bindParams);
+
+ if ($result = pg_query($this->connection, $sql)) {
+ $rows = pg_fetch_all($result);
+ $this->affectedRows = pg_affected_rows($result);
+ pg_free_result($result);
+ return (empty($rows)) ? null : $rows;
+ } else {
+ $this->executeError($sql);
+ }
+ }
+
+ public function getLastInsertId()
+ {
+ $rows = $this->execute("SELECT LASTVAL() AS id");
+ return $rows[0]["id"];
+ }
+
+ private function executeError($sql)
+ {
+ $error = pg_last_error($this->connection);
+ throw new Sabel_Db_Exception_Driver("{$error}, SQL: $sql");
+ }
+}
diff --git a/sabel/db/pgsql/Metadata.php b/sabel/db/pgsql/Metadata.php
new file mode 100755
index 0000000..531c3cc
--- /dev/null
+++ b/sabel/db/pgsql/Metadata.php
@@ -0,0 +1,228 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pgsql_Metadata extends Sabel_Db_Abstract_Metadata
+{
+ /**
+ * @var array
+ */
+ private $sequences = array();
+
+ /**
+ * @var array
+ */
+ private $primaryKeys = array();
+
+ public function getTableList()
+ {
+ $sql = "SELECT table_name FROM information_schema.tables "
+ . "WHERE table_schema = '{$this->schemaName}'";
+
+ $rows = $this->driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $tables = array();
+ foreach ($rows as $row) {
+ $row = array_change_key_case($row);
+ $tables[] = $row["table_name"];
+ }
+
+ return $tables;
+ }
+
+ protected function createColumns($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return array();
+
+ $this->createSequences();
+ $this->createPrimaryKeys($tblName);
+
+ $columns = array();
+ foreach ($rows as $row) {
+ $colName = $row["column_name"];
+ $columns[$colName] = $this->createColumn($row);
+ }
+
+ return $columns;
+ }
+
+ protected function createColumn($row)
+ {
+ $column = new Sabel_Db_Metadata_Column();
+ $column->name = $row["column_name"];
+ $column->nullable = ($row["is_nullable"] !== "NO");
+ Sabel_Db_Type_Manager::create()->applyType($column, $row["data_type"]);
+ $this->setDefault($column, $row["column_default"]);
+
+ $column->primary = (in_array($column->name, $this->primaryKeys));
+ $seq = $row["table_name"] . "_" . $column->name . "_seq";
+ $column->increment = (in_array($seq, $this->sequences));
+
+ if ($column->primary) $column->nullable = false;
+ if ($column->isString()) $this->setLength($column, $row);
+
+ return $column;
+ }
+
+ public function getForeignKeys($tblName)
+ {
+ /*
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+ */
+
+ $sql = <<driver->execute($sql);
+ if (empty($cnames)) return null;
+
+ $fmt = <<driver->execute(sprintf($fmt, $cname["constraint_name"])) as $row) {
+ $column = $row["column_name"];
+ $columns[$column]["referenced_table"] = $row["ref_table"];
+ $columns[$column]["referenced_column"] = $row["ref_column"];
+ $columns[$column]["on_delete"] = $row["delete_rule"];
+ $columns[$column]["on_update"] = $row["update_rule"];
+ }
+ }
+
+ return $columns;
+ }
+
+ public function getUniques($tblName)
+ {
+ $sql = <<driver->execute($sql);
+ if (empty($rows)) return null;
+
+ $uniques = array();
+ foreach ($rows as $row) {
+ $key = $row["constraint_name"];
+ $uniques[$key][] = $row["column_name"];
+ }
+
+ return array_values($uniques);
+ }
+
+ private function createSequences()
+ {
+ if (!empty($this->sequences)) return;
+
+ $seqs =& $this->sequences;
+ $sql = "SELECT relname FROM pg_statio_user_sequences";
+ $rows = $this->driver->execute($sql);
+ if (!$rows) return;
+
+ foreach ($rows as $row) $seqs[] = $row["relname"];
+ }
+
+ private function createPrimaryKeys($tblName)
+ {
+ if (!empty($this->primaryKeys)) return;
+
+ $sql = <<primaryKeys;
+ $rows = $this->driver->execute($sql);
+ if (!$rows) return;
+
+ foreach ($rows as $row) $keys[] = $row["column_name"];
+ }
+
+ private function setDefault($column, $default)
+ {
+ if (strpos($default, "nextval") !== false) {
+ $column->default = null;
+ } else {
+ if (($pos = strpos($default, "::")) !== false) {
+ $default = substr($default, 0, $pos);
+ if ($default{0} === "'") {
+ $default = substr($default, 1, -1);
+ }
+ }
+
+ $this->setDefaultValue($column, $default);
+ }
+ }
+
+ private function setLength($column, $row)
+ {
+ $maxlen = $row["character_maximum_length"];
+ $column->max = ($maxlen === null) ? 255 : (int)$maxlen;
+ }
+}
diff --git a/sabel/db/pgsql/Migration.php b/sabel/db/pgsql/Migration.php
new file mode 100755
index 0000000..b7ebbcb
--- /dev/null
+++ b/sabel/db/pgsql/Migration.php
@@ -0,0 +1,146 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pgsql_Migration extends Sabel_Db_Abstract_Migration
+{
+ protected $types = array(Sabel_Db_Type::INT => "INTEGER",
+ Sabel_Db_Type::BIGINT => "BIGINT",
+ Sabel_Db_Type::SMALLINT => "SMALLINT",
+ Sabel_Db_Type::FLOAT => "REAL",
+ Sabel_Db_Type::DOUBLE => "DOUBLE PRECISION",
+ Sabel_Db_Type::BOOL => "BOOLEAN",
+ Sabel_Db_Type::STRING => "VARCHAR",
+ Sabel_Db_Type::TEXT => "TEXT",
+ Sabel_Db_Type::DATETIME => "TIMESTAMP",
+ Sabel_Db_Type::DATE => "DATE",
+ Sabel_Db_Type::BINARY => "BYTEA");
+
+ protected function createTable($filePath)
+ {
+ $query = $this->getCreateSql($this->getReader($filePath)->readCreate());
+ $this->executeQuery($query);
+ }
+
+ protected function changeColumnUpgrade($columns, $schema)
+ {
+ $this->alterChange($columns, $schema);
+ }
+
+ protected function changeColumnDowngrade($columns, $schema)
+ {
+ $this->alterChange($columns, $schema);
+ }
+
+ protected function createColumnAttributes($column)
+ {
+ $line = array();
+ $line[] = $this->quoteIdentifier($column->name);
+ $line[] = $this->getTypeDefinition($column);
+
+ if ($column->nullable === false) $line[] = "NOT NULL";
+ $line[] = $this->getDefaultValue($column);
+
+ return implode(" ", $line);
+ }
+
+ private function alterChange($columns, $schema)
+ {
+ $tblName = $this->quoteIdentifier($schema->getTableName());
+ $stmt = $this->getStatement();
+ $stmt->getDriver()->begin();
+
+ foreach ($columns as $column) {
+ $current = $schema->getColumnByName($column->name);
+ if ($column->type !== null || ($current->isString() && $column->max !== null)) {
+ $this->changeType($current, $column, $tblName);
+ }
+
+ if ($column->nullable !== null) {
+ $this->changeNullable($current, $column, $tblName);
+ }
+
+ if ($column->default !== $current->default) {
+ $this->changeDefault($column, $tblName);
+ }
+ }
+
+ $stmt->getDriver()->commit();
+ }
+
+ private function changeType($current, $column, $tblName)
+ {
+ $colName = $this->quoteIdentifier($column->name);
+
+ if ($current->type !== $column->type && $column->type !== null) {
+ $type = $this->getTypeDefinition($column);
+ $this->executeQuery("ALTER TABLE $tblName ALTER $colName TYPE $type");
+ } elseif ($current->isString() && $current->max !== $column->max) {
+ $column->type = $current->type;
+ if ($column->max === null) $column->max = 255;
+ $type = $this->getTypeDefinition($column);
+ $this->executeQuery("ALTER TABLE $tblName ALTER $colName TYPE $type");
+ }
+ }
+
+ private function changeNullable($current, $column, $tblName)
+ {
+ if ($current->nullable === $column->nullable) return;
+ $colName = $this->quoteIdentifier($column->name);
+
+ if ($column->nullable) {
+ $this->executeQuery("ALTER TABLE $tblName ALTER $colName DROP NOT NULL");
+ } else {
+ $this->executeQuery("ALTER TABLE $tblName ALTER $colName SET NOT NULL");
+ }
+ }
+
+ private function changeDefault($column, $tblName)
+ {
+ $colName = $this->quoteIdentifier($column->name);
+
+ if ($column->default === _NULL) {
+ $this->executeQuery("ALTER TABLE $tblName ALTER $colName DROP DEFAULT");
+ } else {
+ if ($column->isBool()) {
+ $default = ($column->default) ? "true" : "false";
+ } elseif ($column->isNumeric()) {
+ $default = $column->default;
+ } else {
+ $default = "'{$column->default}'";
+ }
+
+ $this->executeQuery("ALTER TABLE $tblName ALTER $colName SET DEFAULT $default");
+ }
+ }
+
+ private function getTypeDefinition($col)
+ {
+ if ($col->increment) {
+ if ($col->isInt()) {
+ return "serial";
+ } elseif ($col->isBigint()) {
+ return "bigserial";
+ } else {
+ throw new Sabel_Db_Exception("invalid data type for sequence.");
+ }
+ } elseif ($col->isString()) {
+ return $this->types[$col->type] . "({$col->max})";
+ } else {
+ return $this->types[$col->type];
+ }
+ }
+
+ protected function getBooleanAttr($value)
+ {
+ $v = ($value === true) ? "true" : "false";
+ return "DEFAULT " . $v;
+ }
+}
diff --git a/sabel/db/pgsql/Statement.php b/sabel/db/pgsql/Statement.php
new file mode 100755
index 0000000..fd75d3f
--- /dev/null
+++ b/sabel/db/pgsql/Statement.php
@@ -0,0 +1,97 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Pgsql_Statement extends Sabel_Db_Statement
+{
+ protected $binaries = array();
+
+ public function __construct(Sabel_Db_Pgsql_Driver $driver)
+ {
+ $this->driver = $driver;
+ }
+
+ public function clear()
+ {
+ $this->binaries = array();
+ return parent::clear();
+ }
+
+ public function values(array $values)
+ {
+ $columns = $this->metadata->getColumns();
+
+ foreach ($values as $k => &$v) {
+ if (isset($columns[$k]) && $columns[$k]->isBinary()) {
+ $this->binaries[] = $this->createBlob($v);
+ $v = new Sabel_Db_Statement_Expression($this, "__sbl_binary" . count($this->binaries));
+ }
+ }
+
+ $this->values = $values;
+ $this->binds($values);
+
+ return $this;
+ }
+
+ public function escape(array $values)
+ {
+ $conn = $this->driver->getConnection();
+
+ foreach ($values as &$val) {
+ if (is_bool($val)) {
+ $val = ($val) ? "'t'" : "'f'";
+ } elseif (is_string($val)) {
+ $val = "'" . pg_escape_string($conn, $val) . "'";
+ }
+ }
+
+ return $values;
+ }
+
+ public function execute($bindValues = array(), $additionalParameters = array(), $query = null)
+ {
+ $query = $this->getQuery();
+
+ if (!empty($this->binaries)) {
+ for ($i = 0, $c = count($this->binaries); $i < $c; $i++) {
+ $query = str_replace("__sbl_binary" . ($i + 1),
+ $this->binaries[$i]->getData(),
+ $query);
+ }
+ }
+
+ $result = parent::execute($bindValues, $additionalParameters, $query);
+ if (!$this->isSelect() || empty($result)) return $result;
+
+ $binaryColumns = array();
+ foreach ($this->metadata->getColumns() as $column) {
+ if ($column->isBinary()) $binaryColumns[] = $column->name;
+ }
+
+ if (!empty($binaryColumns)) {
+ foreach ($result as &$row) {
+ foreach ($binaryColumns as $colName) {
+ if (isset($row[$colName])) {
+ $row[$colName] = pg_unescape_bytea($row[$colName]);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function createBlob($binary)
+ {
+ $conn = $this->driver->getConnection();
+ return new Sabel_Db_Pgsql_Blob($conn, $binary);
+ }
+}
diff --git a/sabel/db/statement/Expression.php b/sabel/db/statement/Expression.php
new file mode 100755
index 0000000..6f208f0
--- /dev/null
+++ b/sabel/db/statement/Expression.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Statement_Expression extends Sabel_Object
+{
+ protected $stmt = null;
+ protected $expression = "";
+
+ public function __construct(Sabel_Db_Statement $stmt, $expression)
+ {
+ $this->stmt = $stmt;
+ $this->expression = $expression;
+ }
+
+ public function __toString()
+ {
+ return $this->getExpression();
+ }
+
+ public function getExpression()
+ {
+ // @todo ESC()
+ return $this->expression;
+ }
+}
diff --git a/sabel/db/type/Manager.php b/sabel/db/type/Manager.php
new file mode 100755
index 0000000..306a23e
--- /dev/null
+++ b/sabel/db/type/Manager.php
@@ -0,0 +1,196 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Db_Type_Manager
+{
+ private static $instance = null;
+
+ private function __construct()
+ {
+
+ }
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function applyType(Sabel_Db_Metadata_Column $column, $type)
+ {
+ $methods = array(
+ "_int", "_bigint", "_smallint",
+ "_string", "_text", "_boolean",
+ "_datetime", "_date", "_double",
+ "_float", "_binary"
+ );
+
+ $type = strtolower($type);
+ foreach ($methods as $method) {
+ if ($this->$method($column, $type)) {
+ return;
+ }
+ }
+
+ $column->type = Sabel_Db_Type::UNKNOWN;
+ }
+
+ protected function _int($column, $type)
+ {
+ $types = array("integer", "int", "int4", "serial", "tinyint");
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::INT;
+ $column->max = PHP_INT_MAX;
+ $column->min = -PHP_INT_MAX - 1;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _bigint($column, $type)
+ {
+ $types = array("bigint", "int8", "bigserial");
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::BIGINT;
+ $column->max = 9223372036854775807;
+ $column->min = -9223372036854775808;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _smallint($column, $type)
+ {
+ if ($type === "smallint") {
+ $column->type = Sabel_Db_Type::SMALLINT;
+ $column->max = 32767;
+ $column->min = -32768;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _string($column, $type)
+ {
+ $types = array(
+ "varchar", "char", "character varying",
+ "character", "varchar2", "cstring"
+ );
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::STRING;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _text($column, $type)
+ {
+ $types = array(
+ "text", "clob", "mediumtext",
+ "tinytext", "nclob"
+ );
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::TEXT;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _boolean($column, $type)
+ {
+ if ($type === "boolean" || $type === "bit") {
+ $column->type = Sabel_Db_Type::BOOL;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _datetime($column, $type)
+ {
+ $types = array(
+ "timestamp", "timestamp without time zone",
+ "datetime" , "timestamp with time zone"
+ );
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::DATETIME;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _date($column, $type)
+ {
+ if ($type === "date") {
+ $column->type = Sabel_Db_Type::DATE;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _double($column, $type)
+ {
+ $types = array("double", "double precision", "float8");
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::DOUBLE;
+ $column->max = 1.79769E+308;
+ $column->min = -1.79769E+308;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _float($column, $type)
+ {
+ $types = array("float", "real", "float4");
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::FLOAT;
+ $column->max = 3.4028235E+38;
+ $column->min = -3.4028235E+38;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function _binary($column, $type)
+ {
+ $types = array(
+ "blob", "longblob", "mediumblob",
+ "bytea", "varbinary", "binary"
+ );
+
+ if (in_array($type, $types, true)) {
+ $column->type = Sabel_Db_Type::BINARY;
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/sabel/exception/ClassNotFound.php b/sabel/exception/ClassNotFound.php
new file mode 100755
index 0000000..1e79491
--- /dev/null
+++ b/sabel/exception/ClassNotFound.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Exception_ClassNotFound extends Sabel_Exception_Runtime
+{
+
+}
diff --git a/sabel/exception/DirectoryNotFound.php b/sabel/exception/DirectoryNotFound.php
new file mode 100755
index 0000000..ca34e60
--- /dev/null
+++ b/sabel/exception/DirectoryNotFound.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Exception_DirectoryNotFound extends Sabel_Exception_Runtime
+{
+
+}
diff --git a/sabel/exception/FileNotFound.php b/sabel/exception/FileNotFound.php
new file mode 100755
index 0000000..a00a3a0
--- /dev/null
+++ b/sabel/exception/FileNotFound.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Exception_FileNotFound extends Sabel_Exception_Runtime
+{
+
+}
diff --git a/sabel/exception/InvalidArgument.php b/sabel/exception/InvalidArgument.php
new file mode 100755
index 0000000..c1bd410
--- /dev/null
+++ b/sabel/exception/InvalidArgument.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Exception_InvalidArgument extends InvalidArgumentException
+{
+
+}
diff --git a/sabel/exception/Printer.php b/sabel/exception/Printer.php
new file mode 100755
index 0000000..995eb7d
--- /dev/null
+++ b/sabel/exception/Printer.php
@@ -0,0 +1,98 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Exception_Printer
+{
+ public static function printTrace(Exception $exception, $eol = PHP_EOL, $return = false)
+ {
+ $result = array();
+
+ foreach ($exception->getTrace() as $line) {
+ $trace = array();
+
+ if (isset($line["file"])) {
+ $trace[] = "FILE: {$line["file"]}({$line["line"]})";
+ } else {
+ $trace[] = "FILE: Unknown";
+ }
+
+ $args = array();
+ if (isset($line["args"]) && !empty($line["args"])) {
+ foreach ($line["args"] as $arg) {
+ if (is_object($arg)) {
+ $args[] = "(Object)" . get_class($arg);
+ } elseif (is_bool($arg)) {
+ $args[] = ($arg) ? "true" : "false";
+ } elseif (is_string($arg)) {
+ $args[] = '"' . $arg . '"';
+ } elseif (is_int($arg) || is_float($arg)) {
+ $args[] = $arg;
+ } elseif (is_array($arg)) {
+ $args[] = self::arrayToString($arg);
+ } elseif (is_resource($arg)) {
+ $args[] = "(Resource)" . get_resource_type($arg);
+ } elseif ($arg === null) {
+ $args[] = "null";
+ } else {
+ $args[] = "(" . ucfirst(gettype($arg)) . ")" . $arg;
+ }
+ }
+ }
+
+ $args = implode(", ", $args);
+
+ if (isset($line["class"])) {
+ $trace[] = "CALL: " . $line["class"]
+ . $line["type"] . $line["function"] . "({$args})";
+ } else {
+ $trace[] = "FUNCTION: " . $line["function"] . "({$args})";
+ }
+
+ $result[] = implode($eol, $trace);
+ }
+
+ $contents = implode($eol . $eol, $result);
+
+ if ($return) {
+ return $contents;
+ } else {
+ echo $contents;
+ }
+ }
+
+ protected static function arrayToString($array)
+ {
+ $ret = array();
+ foreach ($array as $k => $v) {
+ $k = '"' . $k . '"';
+ if (is_object($v)) {
+ $ret[] = $k . " => (Object)" . get_class($v);
+ } elseif (is_bool($v)) {
+ $str = ($v) ? "true" : "false";
+ $ret[] = $k . " => " . $str;
+ } elseif (is_string($v)) {
+ $ret[] = $k . ' => "' . $v . '"';
+ } elseif (is_int($v) || is_float($v)) {
+ $ret[] = $k . " => " . $v;
+ } elseif (is_array($v)) {
+ $ret[] = $k . " => array(...)";
+ } elseif (is_resource($v)) {
+ $ret[] = $k . " => (Resource)" . get_resource_type($v);
+ } elseif ($v === null) {
+ $ret[] = $k . " => null";
+ } else {
+ $ret[] = $k . " => (" . ucfirst(gettype($v)) . ")" . $v;
+ }
+ }
+
+ return "array(" . implode(", ", $ret) . ")";
+ }
+}
diff --git a/sabel/exception/Runtime.php b/sabel/exception/Runtime.php
new file mode 100755
index 0000000..79050cd
--- /dev/null
+++ b/sabel/exception/Runtime.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Exception_Runtime extends Exception
+{
+ public function writeSyslog($message)
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ openlog("sabel-exception", LOG_PID, LOG_USER);
+ } else {
+ openlog("sabel-exception", LOG_PID, LOG_LOCAL0);
+ }
+
+ $message = str_replace(array("\r\n", "\r"), "\n", $message);
+ $lines = explode("\n", $message);
+
+ foreach ($lines as $line) {
+ syslog(LOG_WARNING, $line);
+ }
+
+ closelog();
+
+ return $lines;
+ }
+}
diff --git a/sabel/functions/core.php b/sabel/functions/core.php
new file mode 100755
index 0000000..95acb12
--- /dev/null
+++ b/sabel/functions/core.php
@@ -0,0 +1,333 @@
+file($path);
+ } elseif (DS === "/") { // *nix
+ $ret = trim(shell_exec("file -ib " . escapeshellcmd($path)));
+ } elseif (function_exists("mime_content_type")) {
+ $ret = mime_content_type($path);
+ }
+
+ if ($tmpFile !== null) {
+ unlink($tmpFile);
+ }
+
+ return $ret;
+}
+
+if (!function_exists("lcfirst")) {
+ function lcfirst($str)
+ {
+ if (!is_string($str) || $str === "") {
+ return "";
+ } else {
+ $str{0} = strtolower($str{0});
+ return $str;
+ }
+ }
+}
+
+function now()
+{
+ return date("Y-m-d H:i:s");
+}
+
+function md5hash()
+{
+ return md5(uniqid(mt_rand(), true));
+}
+
+function sha1hash()
+{
+ return sha1(uniqid(mt_rand(), true));
+}
+
+function load()
+{
+ static $container = null;
+
+ if ($container === null) {
+ $container = Sabel_Container::create();
+ }
+
+ $args = func_get_args();
+
+ return call_user_func_array(array($container, "load"), $args);
+}
+
+function l($message, $level = SBL_LOG_INFO, $identifier = "default")
+{
+ Sabel_Logger::create()->write($message, $level, $identifier);
+}
+
+function normalize_uri($uri)
+{
+ $uri = trim(preg_replace("@/{2,}@", "/", $uri), "/");
+ $parsedUrl = parse_url("http://localhost/{$uri}");
+ return ltrim($parsedUrl["path"], "/");
+}
+
+function is_empty($value)
+{
+ return ($value === null || $value === "" || $value === array() || $value === false);
+}
+
+function array_isset($key, $array)
+{
+ if (($count = preg_match_all('/\[(.+)\]/U', $key, $matches)) > 0) {
+ $key1 = substr($key, 0, strpos($key, "["));
+
+ if (array_isset($key1, $array)) {
+ $array = $array[$key1];
+ foreach ($matches[1] as $_key) {
+ if (array_isset($_key, $array)) {
+ $array = $array[$_key];
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return (isset($array[$key]) && !is_empty($array[$key]));
+ }
+}
+
+function realempty($value)
+{
+ return is_empty($value);
+}
+
+function dump()
+{
+ if (is_cli()) {
+ echo PHP_EOL;
+ echo "================================================" . PHP_EOL;
+ } else {
+ echo '';
+ }
+
+ foreach (func_get_args() as $value) {
+ var_dump($value);
+ }
+
+ if (is_cli()) {
+ echo "================================================" . PHP_EOL;
+ } else {
+ echo ' ';
+ }
+}
+
+function get_server_name()
+{
+ if (defined("SERVICE_DOMAIN")) {
+ return SERVICE_DOMAIN;
+ } elseif (isset($_SERVER["SERVER_NAME"])) {
+ return $_SERVER["SERVER_NAME"];
+ } elseif (isset($_SERVER["HTTP_HOST"])) {
+ return $_SERVER["HTTP_HOST"];
+ } else {
+ return "localhost";
+ }
+}
+
+function is_cli()
+{
+ return (PHP_SAPI === "cli");
+}
+
+function is_ipaddr($arg)
+{
+ if (is_string($arg)) {
+ $ptn = "(0|[1-9][0-9]{0,2})";
+ if (preg_match("/^{$ptn}\.{$ptn}\.{$ptn}\.{$ptn}$/", $arg) === 1) {
+ foreach (explode(".", $arg) as $part) {
+ if ($part > 255) return false;
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+function is_number($num)
+{
+ if (is_int($num)) {
+ return true;
+ } elseif (is_string($num)) {
+ return ($num === "0" || preg_match('/^\-?[1-9][0-9]*$/', $num) === 1);
+ } else {
+ return false;
+ }
+}
+
+function is_natural_number($num)
+{
+ if (is_int($num)) {
+ return ($num >= 0);
+ } elseif (is_string($num)) {
+ return ($num === "0" || preg_match('/^[1-9][0-9]*$/', $num) === 1);
+ } else {
+ return false;
+ }
+}
+
+function strtoint($str)
+{
+ if (is_int($str)) {
+ return $str;
+ } elseif (!is_string($str) || is_empty($str)) {
+ return 0;
+ }
+
+ $len = strlen($str);
+ $char = strtolower($str{$len - 1});
+
+ if (in_array($char, array("k", "m", "g"), true)) {
+ $num = substr($str, 0, $len - 1);
+ if (is_number($num)) {
+ switch ($char) {
+ case "k": return $num * 1024;
+ case "m": return $num * pow(1024, 2);
+ case "g": return $num * pow(1024, 3);
+ default : return 0;
+ }
+ } else {
+ return 0;
+ }
+ } else {
+ return (is_number($str)) ? (int)$str : 0;
+ }
+}
diff --git a/sabel/functions/db.php b/sabel/functions/db.php
new file mode 100755
index 0000000..94a3217
--- /dev/null
+++ b/sabel/functions/db.php
@@ -0,0 +1,230 @@
+setQuery($sql)->binds($params)->execute();
+}
+
+function finder($mdlName, $projection = null)
+{
+ return new Sabel_Db_Finder($mdlName, $projection);
+}
+
+function eq($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::EQUAL, $column, $value
+ );
+}
+
+function neq($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::EQUAL, $column, $value, true
+ );
+}
+
+function in($column, array $values)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::IN, $column, $values
+ );
+}
+
+function nin($column, array $values)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::IN, $column, $values, true
+ );
+}
+
+function lt($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::LESS_THAN, $column, $value
+ );
+}
+
+function le($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::LESS_EQUAL, $column, $value
+ );
+}
+
+function gt($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::GREATER_THAN, $column, $value
+ );
+}
+
+function ge($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::GREATER_EQUAL, $column, $value
+ );
+}
+
+function bw($column, $from, $to = null)
+{
+ if ($to === null) {
+ return __bw($column, $from, false);
+ } else {
+ return __bw($column, array($from, $to), false);
+ }
+}
+
+function nbw($column, $from, $to = null)
+{
+ if ($to === null) {
+ return __bw($column, $from, true);
+ } else {
+ return __bw($column, array($from, $to), true);
+ }
+}
+
+function __bw($column, array $params, $not)
+{
+ if (isset($params["from"])) $params[0] = $params["from"];
+ if (isset($params["to"])) $params[1] = $params["to"];
+
+ unset($params["from"], $params["to"]);
+
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::BETWEEN, $column, $params, $not
+ );
+}
+
+function starts($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::LIKE, $column, $value
+ )->type(Sabel_Db_Condition_Like::STARTS_WITH);
+}
+
+function ends($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::LIKE, $column, $value
+ )->type(Sabel_Db_Condition_Like::ENDS_WITH);
+}
+
+function contains($column, $value)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::LIKE, $column, $value
+ )->type(Sabel_Db_Condition_Like::CONTAINS);
+}
+
+function isNull($column)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::ISNULL, $column
+ );
+}
+
+function isNotNull($column)
+{
+ return Sabel_Db_Condition::create(
+ Sabel_Db_Condition::ISNOTNULL, $column
+ );
+}
+
+function ow(/* args */)
+{
+ $or = new Sabel_Db_Condition_Or();
+ foreach (func_get_args() as $condition) {
+ $or->add($condition);
+ }
+
+ return $or;
+}
+
+function aw(/* args */)
+{
+ $and = new Sabel_Db_Condition_And();
+ foreach (func_get_args() as $condition) {
+ $and->add($condition);
+ }
+
+ return $and;
+}
+
+function rel($mdlName)
+{
+ return new Sabel_Db_Join_Relation($mdlName);
+}
+
+function create_join_key(Sabel_Db_Model $childModel, $parentName)
+{
+ if ($fkey = $childModel->getMetadata()->getForeignKey()) {
+ foreach ($fkey->toArray() as $colName => $fkey) {
+ if ($fkey->table === $parentName) {
+ return array("id" => $fkey->column, "fkey" => $colName);
+ }
+ }
+ }
+
+ return array("id" => "id", "fkey" => $parentName . "_id");
+}
+
+function convert_to_tablename($mdlName)
+{
+ static $cache = array();
+
+ if (isset($cache[$mdlName])) {
+ return $cache[$mdlName];
+ }
+
+ if (preg_match("/^[a-z0-9_]+$/", $mdlName)) {
+ $tblName = $mdlName;
+ } else {
+ $tblName = substr(strtolower(preg_replace("/([A-Z])/", '_$1', $mdlName)), 1);
+ }
+
+ return $cache[$mdlName] = $tblName;
+}
+
+function convert_to_modelname($tblName)
+{
+ static $cache = array();
+
+ if (isset($cache[$tblName])) {
+ return $cache[$tblName];
+ } else {
+ $mdlName = implode("", array_map("ucfirst", explode("_", $tblName)));
+ return $cache[$tblName] = $mdlName;
+ }
+}
diff --git a/sabel/http/Client.php b/sabel/http/Client.php
new file mode 100755
index 0000000..05284a5
--- /dev/null
+++ b/sabel/http/Client.php
@@ -0,0 +1,1232 @@
+ 5,
+ "strictredirects" => false,
+ "useragent" => "Sabel_Http_Client",
+ "timeout" => 10,
+ "adapter" => "Sabel_Http_Client_Adapter_Socket",
+ "httpversion" => self::HTTP_1,
+ "keepalive" => false,
+ "storeresponse" => true,
+ "strict" => true
+ );
+
+ /**
+ * Request URI
+ *
+ * @var Sabel_Http_Uri
+ */
+ protected $uri;
+
+ /**
+ * The adapter used to preform the actual connection to the server
+ *
+ * @var Zend_Http_Client_Adapter_Interface
+ */
+ protected $adapter = null;
+
+ /**
+ * Associative array of request headers
+ *
+ * @var array
+ */
+ protected $headers = array();
+
+ /**
+ * HTTP request method
+ *
+ * @var string
+ */
+ protected $method = self::GET;
+
+ /**
+ * Associative array of GET parameters
+ *
+ * @var array
+ */
+ protected $paramsGet = array();
+
+ /**
+ * Assiciative array of POST parameters
+ *
+ * @var array
+ */
+ protected $paramsPost = array();
+
+ /**
+ * Request body content type (for POST requests)
+ *
+ * @var string
+ */
+ protected $enctype = null;
+
+ /**
+ * The raw post data to send. Could be set by setRawData($data, $enctype).
+ *
+ * @var string
+ */
+ protected $raw_post_data = null;
+
+ /**
+ * HTTP Authentication settings
+ *
+ * Expected to be an associative array with this structure:
+ * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic')
+ * Where 'type' should be one of the supported authentication types (see the AUTH_*
+ * constants), for example 'basic' or 'digest'.
+ *
+ * If null, no authentication will be used.
+ *
+ * @var array|null
+ */
+ protected $auth;
+
+ /**
+ * File upload arrays (used in POST requests)
+ *
+ * An associative array, where each element is of the format:
+ * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents')
+ *
+ * @var array
+ */
+ protected $files = array();
+
+ /**
+ * The client's cookie jar
+ *
+ * @var Zend_Http_CookieJar
+ */
+ protected $cookiejar = null;
+
+ /**
+ * The last HTTP request sent by the client, as string
+ *
+ * @var string
+ */
+ protected $last_request = null;
+
+ /**
+ * The last HTTP response received by the client
+ *
+ * @var Zend_Http_Response
+ */
+ protected $last_response = null;
+
+ /**
+ * Redirection counter
+ *
+ * @var int
+ */
+ protected $redirectCounter = 0;
+
+ /**
+ * Contructor method. Will create a new HTTP client. Accepts the target
+ * URL and optionally configuration array.
+ *
+ * @param Zend_Uri_Http|string $uri
+ * @param array $config Configuration key-value pairs.
+ */
+ public function __construct($uri = null, $config = null)
+ {
+ if ($uri !== null) {
+ $this->setUri($uri);
+ }
+
+ if ($config !== null) {
+ $this->setConfig($config);
+ }
+ }
+
+ /**
+ * Set the URI for the next request
+ *
+ * @param Zend_Uri_Http|string $uri
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setUri($uri)
+ {
+ if (is_string($uri)) {
+ if (($parsed = parse_url($uri)) !== false) {
+ $uri = Sabel_Http_Uri::fromArray($parsed);
+ }
+ }
+
+ if (!$uri instanceof Sabel_Http_Uri) {
+ $message = __METHOD__ . "() Passed parameter is not a valid URI.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ if ($uri->scheme !== "http" && $uri->scheme !== "https") {
+ $message = __METHOD__ . "() Passed parameter is not a valid HTTP URI.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ if (empty($uri->port)) {
+ $uri->port = ($uri->scheme === "http") ? 80 : 443;
+ }
+
+ $this->uri = $uri;
+
+ return $this;
+ }
+
+ /**
+ * Get the URI for the next request
+ *
+ * @param boolean $as_string If true, will return the URI as a string
+ * @return Zend_Uri_Http|string
+ */
+ public function getUri($asString = false)
+ {
+ $uri = $this->uri;
+
+ if ($asString && $uri instanceof Sabel_Http_Uri) {
+ $pass = (empty($uri->pass)) ? "" : ":{$uri->pass}";
+ $auth = (empty($uri->user)) ? "" : "{$uri->user}{$pass}@";
+ $port = (empty($uri->port)) ? "" : ":{$uri->port}";
+ $query = (empty($uri->query)) ? "" : "?{$uri->query}";
+ $fragment = (empty($uri->fragment)) ? "" : "#{$uri->fragment}";
+
+ return $uri->scheme . "://" . $auth . $uri->host . $port . $uri->path . $query . $fragment;
+ } else {
+ return $uri;
+ }
+ }
+
+ /**
+ * Set configuration parameters for this HTTP client
+ *
+ * @param Zend_Config | array $config
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setConfig(array $config = array())
+ {
+ foreach ($config as $k => $v) {
+ $this->config[strtolower($k)] = $v;
+ }
+
+ // Pass configuration options to the adapter if it exists
+ if ($this->adapter instanceof Zend_Http_Client_Adapter_Interface) {
+ $this->adapter->setConfig($config);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the next request's method
+ *
+ * Validated the passed method and sets it. If we have files set for
+ * POST requests, and the new method is not POST, the files are silently
+ * dropped.
+ *
+ * @param string $method
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setMethod($method = self::GET)
+ {
+ if (!preg_match('/^[^\x00-\x1f\x7f-\xff\(\)<>@,;:\\\\"\/\[\]\?={}\s]+$/', $method)) {
+ $message = __METHOD__ . "() '{$method}' is not a valid HTTP request method.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ if ($method === self::POST && $this->enctype === null) {
+ $this->setEncType(self::ENC_URLENCODED);
+ }
+
+ $this->method = $method;
+
+ return $this;
+ }
+
+ /**
+ * Set one or more request headers
+ *
+ * This function can be used in several ways to set the client's request
+ * headers:
+ * 1. By providing two parameters: $name as the header to set (eg. 'Host')
+ * and $value as it's value (eg. 'www.example.com').
+ * 2. By providing a single header string as the only parameter
+ * eg. 'Host: www.example.com'
+ * 3. By providing an array of headers as the first parameter
+ * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case
+ * the function will call itself recursively for each array item.
+ *
+ * @param string|array $name Header name, full header string ('Header: value')
+ * or an array of headers
+ * @param mixed $value Header value or null
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setHeaders($name, $value = null)
+ {
+ // If we got an array, go recusive!
+ if (is_array($name)) {
+ foreach ($name as $k => $v) {
+ if (is_string($k)) {
+ $this->setHeaders($k, $v);
+ } else {
+ $this->setHeaders($v, null);
+ }
+ }
+ } else {
+ // Check if $name needs to be split
+ if ($value === null && (strpos($name, ":") > 0)) {
+ list($name, $value) = explode(":", $name, 2);
+ }
+
+ // Make sure the name is valid if we are in strict mode
+ if ($this->config["strict"] && (!preg_match('/^[a-zA-Z0-9-]+$/', $name))) {
+ $message = __METHOD__ . "() {$name} is not a valid HTTP header name.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $normalized_name = strtolower($name);
+
+ // If $value is null or false, unset the header
+ if ($value === null || $value === false) {
+ unset($this->headers[$normalized_name]);
+ } else {
+ // Header names are stored lowercase internally.
+ if (is_string($value)) {
+ $value = trim($value);
+ }
+
+ $this->headers[$normalized_name] = array($name, $value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the value of a specific header
+ *
+ * Note that if the header has more than one value, an array
+ * will be returned.
+ *
+ * @param string $key
+ * @return string|array|null The header value or null if it is not set
+ */
+ public function getHeader($key)
+ {
+ $key = strtolower($key);
+ if (isset($this->headers[$key])) {
+ return $this->headers[$key][1];
+ } else {
+ return null;
+ }
+ }
+
+ public function value($name, $value = null)
+ {
+ if ($this->method === self::GET) {
+ $this->setParameterGet($name, $value);
+ } elseif ($this->method === self::POST) {
+ $this->setParameterPost($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a GET parameter for the request. Wrapper around _setParameter
+ *
+ * @param string|array $name
+ * @param string $value
+ * @return Zend_Http_Client
+ */
+ public function setParameterGet($name, $value = null)
+ {
+ if (is_array($name)) {
+ foreach ($name as $k => $v) {
+ $this->_setParameter("GET", $k, $v);
+ }
+ } else {
+ $this->_setParameter("GET", $name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a POST parameter for the request. Wrapper around _setParameter
+ *
+ * @param string|array $name
+ * @param string $value
+ * @return Zend_Http_Client
+ */
+ public function setParameterPost($name, $value = null)
+ {
+ if (is_array($name)) {
+ foreach ($name as $k => $v) {
+ $this->_setParameter("POST", $k, $v);
+ }
+ } else {
+ $this->_setParameter("POST", $name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost
+ *
+ * @param string $type GET or POST
+ * @param string $name
+ * @param string $value
+ * @return null
+ */
+ protected function _setParameter($type, $name, $value)
+ {
+ $parray = array();
+ $type = strtolower($type);
+
+ switch ($type) {
+ case "get":
+ $parray =& $this->paramsGet;
+ break;
+ case "post":
+ $parray =& $this->paramsPost;
+ break;
+ }
+
+ if ($value === null) {
+ if (isset($parray[$name])) unset($parray[$name]);
+ } else {
+ $parray[$name] = $value;
+ }
+ }
+
+ /**
+ * Get the number of redirections done on the last request
+ *
+ * @return int
+ */
+ public function getRedirectionsCount()
+ {
+ return $this->redirectCounter;
+ }
+
+ /**
+ * Set HTTP authentication parameters
+ *
+ * $type should be one of the supported types - see the self::AUTH_*
+ * constants.
+ *
+ * To enable authentication:
+ *
+ * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC);
+ *
+ *
+ * To disable authentication:
+ *
+ * $this->setAuth(false);
+ *
+ *
+ * @see http://www.faqs.org/rfcs/rfc2617.html
+ * @param string|false $user User name or false disable authentication
+ * @param string $password Password
+ * @param string $type Authentication type
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setAuth($user, $password = '', $type = self::AUTH_BASIC)
+ {
+ if ($user === false || $user === null) {
+ $this->auth = null;
+ } else {
+ if (!defined("self::AUTH_" . strtoupper($type))) {
+ $message = __METHOD__ . "() Invalid or not supported authentication type: '{$type}'.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $this->auth = array(
+ "user" => (string)$user,
+ "password" => (string)$password,
+ "type" => $type
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the HTTP client's cookie jar.
+ *
+ * A cookie jar is an object that holds and maintains cookies across HTTP requests
+ * and responses.
+ *
+ * @param Zend_Http_CookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setCookieJar($cookiejar = true)
+ {
+ if ($cookiejar instanceof Sabel_Http_CookieJar) {
+ $this->cookiejar = $cookiejar;
+ } elseif ($cookiejar === true) {
+ $this->cookiejar = new Sabel_Http_CookieJar();
+ } elseif (!$cookiejar) {
+ $this->cookiejar = null;
+ } else {
+ $message = __METHOD__ . "() Invalid parameter type passed as CookieJar.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the current cookie jar or null if none.
+ *
+ * @return Zend_Http_CookieJar|null
+ */
+ public function getCookieJar()
+ {
+ return $this->cookiejar;
+ }
+
+ /**
+ * Add a cookie to the request. If the client has no Cookie Jar, the cookies
+ * will be added directly to the headers array as "Cookie" headers.
+ *
+ * @param Zend_Http_Cookie|string $cookie
+ * @param string|null $value If "cookie" is a string, this is the cookie value.
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setCookie($cookie, $value = null)
+ {
+ if (is_array($cookie)) {
+ foreach ($cookie as $c => $v) {
+ if (is_string($c)) {
+ $this->setCookie($c, $v);
+ } else {
+ $this->setCookie($v);
+ }
+ }
+
+ return $this;
+ }
+
+ if ($value !== null) {
+ $value = urlencode($value);
+ }
+
+ if (isset($this->cookiejar)) {
+ if ($cookie instanceof Sabel_Http_Cookie) {
+ $this->cookiejar->addCookie($cookie);
+ } elseif (is_string($cookie) && $value !== null) {
+ $cookie = Sabel_Http_Cookie::fromString("{$cookie}={$value}", $this->uri);
+ $this->cookiejar->addCookie($cookie);
+ }
+ } else {
+ if ($cookie instanceof Sabel_Http_Cookie) {
+ $name = $cookie->getName();
+ $value = $cookie->getValue();
+ $cookie = $name;
+ }
+
+ if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) {
+ $message = __METHOD__ . "() Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $value = addslashes($value);
+
+ if (!isset($this->headers["cookie"])) {
+ $this->headers["cookie"] = array("Cookie", "");
+ }
+
+ $this->headers["cookie"][1] .= $cookie . "=" . $value . "; ";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a file to upload (using a POST request)
+ *
+ * Can be used in two ways:
+ *
+ * 1. $data is null (default): $filename is treated as the name if a local file which
+ * will be read and sent. Will try to guess the content type using mime_content_type().
+ * 2. $data is set - $filename is sent as the file name, but $data is sent as the file
+ * contents and no file is read from the file system. In this case, you need to
+ * manually set the Content-Type ($ctype) or it will default to
+ * application/octet-stream.
+ *
+ * @param string $filename Name of file to upload, or name to save as
+ * @param string $formname Name of form element to send as
+ * @param string $data Data to send (if null, $filename is read and sent)
+ * @param string $ctype Content type to use (if $data is set and $ctype is
+ * null, will be application/octet-stream)
+ * @return Zend_Http_Client
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setFileUpload($filename, $formname, $data = null, $ctype = null)
+ {
+ if ($data === null) {
+ if (($data = @file_get_contents($filename)) === false) {
+ $message = __METHOD__ . "() Unable to read file '{$filename}' for upload.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if (!$ctype) {
+ $mimeType = get_mime_type($filename);
+ $ctype = ($mimeType) ? $mimeType : "application/octet-stream";
+ }
+ }
+
+ // Force enctype to multipart/form-data
+ $this->setEncType(self::ENC_FORMDATA);
+
+ $this->files[] = array(
+ "formname" => $formname,
+ "filename" => basename($filename),
+ "ctype" => $ctype,
+ "data" => $data
+ );
+
+ return $this;
+ }
+
+ /**
+ * Set the encoding type for POST data
+ *
+ * @param string $enctype
+ * @return Zend_Http_Client
+ */
+ public function setEncType($enctype = self::ENC_URLENCODED)
+ {
+ $this->enctype = $enctype;
+
+ return $this;
+ }
+
+ /**
+ * Set the raw (already encoded) POST data.
+ *
+ * This function is here for two reasons:
+ * 1. For advanced user who would like to set their own data, already encoded
+ * 2. For backwards compatibilty: If someone uses the old post($data) method.
+ * this method will be used to set the encoded data.
+ *
+ * @param string $data
+ * @param string $enctype
+ * @return Zend_Http_Client
+ */
+ public function setRawData($data, $enctype = null)
+ {
+ $this->raw_post_data = $data;
+ $this->setEncType($enctype);
+
+ return $this;
+ }
+
+ /**
+ * Clear all GET and POST parameters
+ *
+ * Should be used to reset the request parameters if the client is
+ * used for several concurrent requests.
+ *
+ * @return Zend_Http_Client
+ */
+ public function resetParameters()
+ {
+ // Reset parameter data
+ $this->paramsGet = array();
+ $this->paramsPost = array();
+ $this->files = array();
+ $this->raw_post_data = null;
+
+ // Clear outdated headers
+ if (isset($this->headers[strtolower(self::CONTENT_TYPE)])) {
+ unset($this->headers[strtolower(self::CONTENT_TYPE)]);
+ }
+
+ if (isset($this->headers[strtolower(self::CONTENT_LENGTH)])) {
+ unset($this->headers[strtolower(self::CONTENT_LENGTH)]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the last HTTP request as string
+ *
+ * @return string
+ */
+ public function getLastRequest()
+ {
+ return $this->last_request;
+ }
+
+ /**
+ * Get the last HTTP response received by this client
+ *
+ * If $config['storeresponse'] is set to false, or no response was
+ * stored yet, will return null
+ *
+ * @return Zend_Http_Response or null if none
+ */
+ public function getLastResponse()
+ {
+ return $this->last_response;
+ }
+
+ /**
+ * Load the connection adapter
+ *
+ * While this method is not called more than one for a client, it is
+ * seperated from ->request() to preserve logic and readability
+ *
+ * @param Zend_Http_Client_Adapter_Interface|string $adapter
+ * @return null
+ * @throws Zend_Http_Client_Exception
+ */
+ public function setAdapter($adapter)
+ {
+ if (is_string($adapter)) {
+ if (!Sabel::using($adapter)) {
+ throw new Sabel_Exception_ClassNotFound($adapter);
+ }
+
+ $adapter = new $adapter();
+ }
+
+ if (!$adapter instanceof Sabel_Http_Client_Adapter_Interface) {
+ $message = __METHOD__ . "() Passed adapter is not a HTTP connection adapter.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $this->adapter = $adapter;
+
+ $config = $this->config;
+ unset($config["adapter"]);
+ $this->adapter->setConfig($config);
+ }
+
+ /**
+ * Send the HTTP request and return an HTTP response object
+ *
+ * @param string $method
+ * @return Zend_Http_Response
+ * @throws Zend_Http_Client_Exception
+ */
+ public function request($method = null)
+ {
+ if (!$this->uri instanceof Sabel_Http_Uri) {
+ $message = __METHOD__ . "() No valid URI has been passed to the client.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if ($method) {
+ $this->setMethod($method);
+ }
+
+ $this->redirectCounter = 0;
+ $response = null;
+
+ // Make sure the adapter is loaded
+ if ($this->adapter == null) {
+ $this->setAdapter($this->config["adapter"]);
+ }
+
+ // Send the first request. If redirected, continue.
+ do {
+ // Clone the URI and add the additional GET parameters to it
+ $uri = clone $this->uri;
+
+ $query = array();
+ if (!empty($uri->query)) {
+ parse_str($uri->query, $query);
+ }
+
+ if (!empty($this->paramsGet)) {
+ $query = array_merge($query, $this->paramsGet);
+ }
+
+ if (!empty($query)) {
+ $uri->query = http_build_query($query, null, "&");
+ }
+
+ $body = $this->_prepareBody();
+ $headers = $this->_prepareHeaders();
+
+ // Open the connection, send the request and read the response
+ $this->adapter->connect($uri->host, $uri->port, ($uri->scheme === "https") ? true : false);
+
+ $this->last_request = $this->adapter->write(
+ $this->method, $uri, $this->config["httpversion"], $headers, $body
+ );
+
+ $response = $this->adapter->read();
+
+ if (!$response) {
+ $message = __METHOD__ . "() Unable to read response, or response is empty.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $response = Sabel_Http_Response::fromString($response);
+ if ($this->config["storeresponse"]) {
+ $this->last_response = $response;
+ }
+
+ // Load cookies into cookie jar
+ if (isset($this->cookiejar)) {
+ $this->cookiejar->addCookiesFromResponse($response, $uri);
+ }
+
+ // If we got redirected, look for the Location header
+ if ($response->isRedirect() && ($location = $response->getHeader("location"))) {
+ // Check whether we send the exact same request again,
+ // or drop the parameters and send a GET request
+ if ($response->getStatus() == 303 ||
+ ((!$this->config["strictredirects"]) && ($response->getStatus() == 302 ||
+ $response->getStatus() == 301))) {
+ $this->resetParameters();
+ $this->setMethod(self::GET);
+ }
+
+ // If we got a well formed absolute URI
+
+ $_uri = @parse_url($location);
+ if (isset($_uri["scheme"]) && isset($_uri["host"])) {
+ $this->setHeaders("host", null);
+ $this->setUri($location);
+ } else {
+ // Split into path and query and set the query
+ if (strpos($location, "?") === false) {
+ $query = "";
+ } else {
+ list($location, $query) = explode("?", $location, 2);
+ }
+
+ $this->uri->query = $query;
+
+ // Else, if we got just an absolute path, set it
+ if(strpos($location, "/") === 0) {
+ $this->uri->path = $location;
+ } else {
+ // Get the current path directory, removing any trailing slashes
+ $path = $this->uri->path;
+ $path = rtrim(substr($path, 0, strrpos($path, "/")), "/");
+ $this->uri->path = $path . "/" . $location;
+ }
+ }
+
+ ++$this->redirectCounter;
+ } else {
+ // If we didn't get any location, stop redirecting
+ break;
+ }
+ } while ($this->redirectCounter < $this->config["maxredirects"]);
+
+ return $response;
+ }
+
+ /**
+ * Prepare the request headers
+ *
+ * @return array
+ */
+ protected function _prepareHeaders()
+ {
+ $uri = $this->uri;
+ $headers = array();
+
+ // Set the host header
+ if (!isset($this->headers["host"])) {
+ $host = $uri->host;
+ $port = (int)$uri->port;
+
+ // If the port is not default, add it
+ if (!(($uri->scheme === "http" && $port === 80) || ($uri->scheme === "https" && $port === 443))) {
+ $host .= ":" . $port;
+ }
+
+ $headers[] = "Host: {$host}";
+ }
+
+ // Set the connection header
+ if (!isset($this->headers["connection"])) {
+ if (!$this->config["keepalive"]) {
+ $headers[] = "Connection: close";
+ }
+ }
+
+ // Set the Accept-encoding header if not set - depending on whether
+ // zlib is available or not.
+ if (!isset($this->headers["accept-encoding"])) {
+ if (function_exists("gzinflate")) {
+ $headers[] = "Accept-encoding: gzip, deflate";
+ } else {
+ $headers[] = "Accept-encoding: identity";
+ }
+ }
+
+ // Set the Content-Type header
+ if ($this->method === self::POST &&
+ (!isset($this->headers[strtolower(self::CONTENT_TYPE)]) && isset($this->enctype))) {
+ $headers[] = self::CONTENT_TYPE . ": " . $this->enctype;
+ }
+
+ // Set the user agent header
+ if (!isset($this->headers["user-agent"]) && isset($this->config["useragent"])) {
+ $headers[] = "User-Agent: {$this->config['useragent']}";
+ }
+
+ // Set HTTP authentication if needed
+ if (is_array($this->auth)) {
+ $auth = self::encodeAuthHeader($this->auth["user"], $this->auth["password"], $this->auth["type"]);
+ $headers[] = "Authorization: {$auth}";
+ }
+
+ // Load cookies from cookie jar
+ if (isset($this->cookiejar)) {
+ $cookstr = $this->cookiejar->getMatchingCookies(
+ $uri, true, Sabel_Http_CookieJar::COOKIE_STRING_CONCAT
+ );
+
+ if ($cookstr) {
+ $headers[] = "Cookie: {$cookstr}";
+ }
+ }
+
+ // Add all other user defined headers
+ foreach ($this->headers as $header) {
+ list($name, $value) = $header;
+ if (is_array($value)) {
+ $value = implode(", ", $value);
+ }
+
+ $headers[] = "$name: $value";
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Prepare the request body (for POST and PUT requests)
+ *
+ * @return string
+ * @throws Zend_Http_Client_Exception
+ */
+ protected function _prepareBody()
+ {
+ // According to RFC2616, a TRACE request should not have a body.
+ if ($this->method === self::TRACE) {
+ return "";
+ }
+
+ $mbIntEnc = null;
+
+ // If mbstring overloads substr and strlen functions, we have to
+ // override it's internal encoding
+ if (function_exists("mb_internal_encoding") && ((int)ini_get("mbstring.func_overload")) & 2) {
+ $mbIntEnc = mb_internal_encoding();
+ mb_internal_encoding("ASCII");
+ }
+
+ // If we have raw_post_data set, just use it as the body.
+ if (isset($this->raw_post_data)) {
+ $this->setHeaders(self::CONTENT_LENGTH, strlen($this->raw_post_data));
+
+ if ($mbIntEnc !== null) {
+ mb_internal_encoding($mbIntEnc);
+ }
+
+ return $this->raw_post_data;
+ }
+
+ $body = "";
+
+ // If we have files to upload, force enctype to multipart/form-data
+ if (count($this->files) > 0) {
+ $this->setEncType(self::ENC_FORMDATA);
+ }
+
+ // If we have POST parameters or files, encode and add them to the body
+ if (count($this->paramsPost) > 0 || count($this->files) > 0) {
+ switch($this->enctype) {
+ case self::ENC_FORMDATA:
+ // Encode body as multipart/form-data
+ $boundary = md5hash();
+ $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}");
+
+ // Get POST parameters and encode them
+ $params = self::_flattenParametersArray($this->paramsPost);
+ foreach ($params as $pp) {
+ $body .= self::encodeFormData($boundary, $pp[0], $pp[1]);
+ }
+
+ // Encode files
+ foreach ($this->files as $file) {
+ $fhead = array(self::CONTENT_TYPE => $file["ctype"]);
+ $body .= self::encodeFormData($boundary, $file["formname"], $file["data"], $file["filename"], $fhead);
+ }
+
+ $body .= "--{$boundary}--\r\n";
+ break;
+
+ case self::ENC_URLENCODED:
+ // Encode body as application/x-www-form-urlencoded
+ $this->setHeaders(self::CONTENT_TYPE, self::ENC_URLENCODED);
+ $body = http_build_query($this->paramsPost, "", "&");
+ break;
+
+ default:
+ if (isset($mbIntEnc)) {
+ mb_internal_encoding($mbIntEnc);
+ }
+
+ $message = __METHOD__ . "() Cannot handle content type '{$this->enctype}' automatically."
+ . " Please use Sabel_Http_Client::setRawData to send this kind of content.";
+
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ // Set the Content-Length if we have a body or if request is POST/PUT
+ if ($body || $this->method === self::POST || $this->method === self::PUT) {
+ $this->setHeaders(self::CONTENT_LENGTH, strlen($body));
+ }
+
+ if (isset($mbIntEnc)) {
+ mb_internal_encoding($mbIntEnc);
+ }
+
+ return $body;
+ }
+
+ /**
+ * Helper method that gets a possibly multi-level parameters array (get or
+ * post) and flattens it.
+ *
+ * The method returns an array of (key, value) pairs (because keys are not
+ * necessarily unique. If one of the parameters in as array, it will also
+ * add a [] suffix to the key.
+ *
+ * This method is deprecated since Zend Framework 1.9 in favour of
+ * self::_flattenParametersArray() and will be dropped in 2.0
+ *
+ * @deprecated since 1.9
+ *
+ * @param array $parray The parameters array
+ * @param bool $urlencode Whether to urlencode the name and value
+ * @return array
+ */
+ protected function _getParametersRecursive($parray, $urlencode = false)
+ {
+ // Issue a deprecated notice
+ trigger_error("The " . __METHOD__ . " method is deprecated and will be dropped in 2.0.", E_USER_NOTICE);
+
+ if (!is_array($parray)) {
+ return $parray;
+ }
+
+ $parameters = array();
+
+ foreach ($parray as $name => $value) {
+ if ($urlencode) {
+ $name = urlencode($name);
+ }
+
+ // If $value is an array, iterate over it
+ if (is_array($value)) {
+ $name .= ($urlencode ? "%5B%5D" : "[]");
+ foreach ($value as $subval) {
+ if ($urlencode) {
+ $subval = urlencode($subval);
+ }
+
+ $parameters[] = array($name, $subval);
+ }
+ } else {
+ if ($urlencode) {
+ $value = urlencode($value);
+ }
+
+ $parameters[] = array($name, $value);
+ }
+ }
+
+ return $parameters;
+ }
+
+ /**
+ * Encode data to a multipart/form-data part suitable for a POST request.
+ *
+ * @param string $boundary
+ * @param string $name
+ * @param mixed $value
+ * @param string $filename
+ * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary")
+ * @return string
+ */
+ public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array())
+ {
+ $ret = "--{$boundary}\r\n" . 'Content-Disposition: form-data; name="' . $name .'"';
+
+ if ($filename) {
+ $ret .= '; filename="' . $filename . '"';
+ }
+
+ $ret .= "\r\n";
+
+ foreach ($headers as $hname => $hvalue) {
+ $ret .= "{$hname}: {$hvalue}\r\n";
+ }
+
+ $ret .= "\r\n";
+ $ret .= "{$value}\r\n";
+
+ return $ret;
+ }
+
+ /**
+ * Create a HTTP authentication "Authorization:" header according to the
+ * specified user, password and authentication method.
+ *
+ * @see http://www.faqs.org/rfcs/rfc2617.html
+ * @param string $user
+ * @param string $password
+ * @param string $type
+ * @return string
+ * @throws Zend_Http_Client_Exception
+ */
+ public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC)
+ {
+ $authHeader = null;
+
+ switch ($type) {
+ case self::AUTH_BASIC:
+ // In basic authentication, the user name cannot contain ":"
+ if (strpos($user, ":") !== false) {
+ $message = __METHOD__ . "() The user name cannot contain ':' in 'Basic' HTTP authentication";
+ throw new Sabel_Exception_Runtime("The user name cannot contain ':' in 'Basic' HTTP authentication");
+ }
+
+ $authHeader = "Basic " . base64_encode($user . ":" . $password);
+ break;
+
+ // @todo Implement digest authentication
+ // case self::AUTH_DIGEST:
+ // break;
+
+ default:
+ $message = __METHOD__ . "() Not a supported HTTP authentication type: '{$type}'";
+ throw new Sabel_Exception_Runtime("Not a supported HTTP authentication type: '$type'");
+ }
+
+ return $authHeader;
+ }
+
+ /**
+ * Convert an array of parameters into a flat array of (key, value) pairs
+ *
+ * Will flatten a potentially multi-dimentional array of parameters (such
+ * as POST parameters) into a flat array of (key, value) paris. In case
+ * of multi-dimentional arrays, square brackets ([]) will be added to the
+ * key to indicate an array.
+ *
+ * @since 1.9
+ *
+ * @param array $parray
+ * @param string $prefix
+ * @return array
+ */
+ static protected function _flattenParametersArray($parray, $prefix = null)
+ {
+ if (!is_array($parray)) {
+ return $parray;
+ }
+
+ $parameters = array();
+
+ foreach($parray as $name => $value) {
+ // Calculate array key
+ if ($prefix) {
+ if (is_int($name)) {
+ $key = $prefix . "[]";
+ } else {
+ $key = $prefix . "[{$name}]";
+ }
+ } else {
+ $key = $name;
+ }
+
+ if (is_array($value)) {
+ $parameters = array_merge($parameters, self::_flattenParametersArray($value, $key));
+ } else {
+ $parameters[] = array($key, $value);
+ }
+ }
+
+ return $parameters;
+ }
+}
diff --git a/sabel/http/Cookie.php b/sabel/http/Cookie.php
new file mode 100755
index 0000000..16c5b01
--- /dev/null
+++ b/sabel/http/Cookie.php
@@ -0,0 +1,392 @@
+name = (string)$name) {
+ $message = __METHOD__ . "() Cookies must have a name.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ if (! $this->domain = (string) $domain) {
+ $message = __METHOD__ . "() Cookies must have a domain.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $this->value = (string)$value;
+ $this->expires = ($expires === null ? null : (int)$expires);
+ $this->path = ($path ? $path : "/");
+ $this->secure = $secure;
+ }
+
+ /**
+ * Get Cookie name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get cookie value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Get cookie domain
+ *
+ * @return string
+ */
+ public function getDomain()
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Get the cookie path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Get the expiry time of the cookie, or null if no expiry time is set
+ *
+ * @return int|null
+ */
+ public function getExpiryTime()
+ {
+ return $this->expires;
+ }
+
+ /**
+ * Check whether the cookie should only be sent over secure connections
+ *
+ * @return boolean
+ */
+ public function isSecure()
+ {
+ return $this->secure;
+ }
+
+ /**
+ * Check whether the cookie has expired
+ *
+ * Always returns false if the cookie is a session cookie (has no expiry time)
+ *
+ * @param int $now Timestamp to consider as "now"
+ * @return boolean
+ */
+ public function isExpired($now = null)
+ {
+ if ($now === null) {
+ $now = time();
+ }
+
+ return (is_int($this->expires) && $this->expires < $now);
+ }
+
+ /**
+ * Check whether the cookie is a session cookie (has no expiry time set)
+ *
+ * @return boolean
+ */
+ public function isSessionCookie()
+ {
+ return ($this->expires === null);
+ }
+
+ /**
+ * Checks whether the cookie should be sent or not in a specific scenario
+ *
+ * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path)
+ * @param boolean $matchSessionCookies Whether to send session cookies
+ * @param int $now Override the current time when checking for expiry time
+ * @return boolean
+ */
+ public function match($uri, $matchSessionCookies = true, $now = null)
+ {
+ if (is_string($uri)) {
+ if (($parsed = parse_url($uri)) !== false) {
+ $uri = Sabel_Http_Uri::fromArray($parsed);
+ } else {
+ $uri = false;
+ }
+ }
+
+ // Make sure we have a valid Zend_Uri_Http object
+ if (!$uri && ($uri->scheme === "http" || $uri->scheme === "https")) {
+ $message = __METHOD__ . "() Passed URI is not a valid HTTP or HTTPS URI.";
+ throw new Sabel_Exception_Runtime('Passed URI is not a valid HTTP or HTTPS URI');
+ }
+
+ // Check that the cookie is secure (if required) and not expired
+ if ($this->secure && $uri->scheme !== "https") {
+ return false;
+ }
+
+ if ($this->isExpired($now)) {
+ return false;
+ }
+
+ if ($this->isSessionCookie() && ! $matchSessionCookies) {
+ return false;
+ }
+
+ // Check if the domain matches
+ if (!self::matchCookieDomain($this->getDomain(), $uri->host)) {
+ return false;
+ }
+
+ // Check that path matches using prefix match
+ if (!self::matchCookiePath($this->getPath(), $uri->path)) {
+ return false;
+ }
+
+ // If we didn't die until now, return true.
+ return true;
+ }
+
+ /**
+ * Get the cookie as a string, suitable for sending as a "Cookie" header in an
+ * HTTP request
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->name . "=" . urlencode($this->value) . ";";
+ }
+
+ /**
+ * Generate a new Cookie object from a cookie string
+ * (for example the value of the Set-Cookie HTTP header)
+ *
+ * @param string $cookieStr
+ * @param Zend_Uri_Http|string $ref_uri Reference URI for default values (domain, path)
+ * @return Zend_Http_Cookie A new Zend_Http_Cookie object or false on failure.
+ */
+ public static function fromString($cookieStr, $ref_uri = null)
+ {
+ if (is_string($ref_uri)) {
+ if (($parsed = parse_url($ref_uri)) !== false) {
+ $ref_uri = Sabel_Http_Uri::fromArray($parsed);
+ }
+ }
+
+ $name = "";
+ $value = "";
+ $domain = "";
+ $path = "";
+ $expires = null;
+ $secure = false;
+ $parts = explode(";", $cookieStr);
+
+ // If first part does not include '=', fail
+ if (strpos($parts[0], "=") === false) {
+ return false;
+ }
+
+ // Get the name and value of the cookie
+ list($name, $value) = explode("=", trim(array_shift($parts)), 2);
+ $name = trim($name);
+ $value = urldecode(trim($value));
+
+ // Set default domain and path
+ if ($ref_uri instanceof Sabel_Http_Uri) {
+ $domain = $ref_uri->host;
+ $path = $ref_uri->path;
+ $path = substr($path, 0, strrpos($path, "/"));
+ }
+
+ // Set other cookie parameters
+ foreach ($parts as $part) {
+ $part = trim($part);
+
+ if (strtolower($part) === "secure") {
+ $secure = true;
+ continue;
+ }
+
+ $keyValue = explode("=", $part, 2);
+
+ if (count($keyValue) === 2) {
+ list($k, $v) = $keyValue;
+ switch (strtolower($k)) {
+ case "expires":
+ if (($expires = strtotime($v)) === false) {
+ /**
+ * The expiration is past Tue, 19 Jan 2038 03:14:07 UTC
+ * the maximum for 32-bit signed integer. Zend_Date
+ * can get around that limit.
+ *
+ * @see Zend_Date
+ */
+ //require_once 'Zend/Date.php';
+ //$expireDate = new Zend_Date($v);
+ //$expires = $expireDate->getTimestamp();
+ }
+ break;
+
+ case "path":
+ $path = $v;
+ break;
+
+ case "domain":
+ $domain = $v;
+ break;
+ }
+ }
+ }
+
+ if ($name !== "") {
+ return new self($name, $value, $domain, $expires, $path, $secure);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Check if a cookie's domain matches a host name.
+ *
+ * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching
+ *
+ * @param string $cookieDomain
+ * @param string $host
+ *
+ * @return boolean
+ */
+ public static function matchCookieDomain($cookieDomain, $host)
+ {
+ if (!$cookieDomain) {
+ $message = __METHOD__ . "() {$cookieDomain} is expected to be a cookie domain.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if (!$host) {
+ $message = __METHOD__ . "() {$host} is expected to be a host name.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $cookieDomain = strtolower($cookieDomain);
+ $host = strtolower($host);
+
+ if ($cookieDomain[0] == ".") {
+ $cookieDomain = substr($cookieDomain, 1);
+ }
+
+ // Check for either exact match or suffix match
+ return ($cookieDomain == $host || preg_match('/\.' . $cookieDomain . '$/', $host));
+ }
+
+ /**
+ * Check if a cookie's path matches a URL path
+ *
+ * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching
+ *
+ * @param string $cookiePath
+ * @param string $path
+ * @return boolean
+ */
+ public static function matchCookiePath($cookiePath, $path)
+ {
+ if (!$cookiePath) {
+ $message = __METHOD__ . "() {$cookiePath} is expected to be a cookie path.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if (!$path) {
+ $message = __METHOD__ . "() {$path} is expected to be a host name.";
+ throw new Sabel_Exception_Runtime("\$path is expected to be a host name");
+ }
+
+ return (strpos($path, $cookiePath) === 0);
+ }
+}
diff --git a/sabel/http/CookieJar.php b/sabel/http/CookieJar.php
new file mode 100755
index 0000000..0a3a0b2
--- /dev/null
+++ b/sabel/http/CookieJar.php
@@ -0,0 +1,369 @@
+getDomain();
+ $path = $cookie->getPath();
+ if (!isset($this->cookies[$domain])) $this->cookies[$domain] = array();
+ if (!isset($this->cookies[$domain][$path])) $this->cookies[$domain][$path] = array();
+ $this->cookies[$domain][$path][$cookie->getName()] = $cookie;
+ $this->_rawCookies[] = $cookie;
+ } else {
+ $message = __METHOD__ . "() Supplient argument is not a valid cookie string or object";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ /**
+ * Parse an HTTP response, adding all the cookies set in that response
+ * to the cookie jar.
+ *
+ * @param Zend_Http_Response $response
+ * @param Zend_Uri_Http|string $ref_uri Requested URI
+ */
+ public function addCookiesFromResponse($response, $ref_uri)
+ {
+ if (!$response instanceof Sabel_Http_Response) {
+ $message = __METHOD__ . "() \$response is expected to be a Response object.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $cookie_hdrs = $response->getHeader("Set-Cookie");
+
+ if (is_array($cookie_hdrs)) {
+ foreach ($cookie_hdrs as $cookie) {
+ $this->addCookie($cookie, $ref_uri);
+ }
+ } elseif (is_string($cookie_hdrs)) {
+ $this->addCookie($cookie_hdrs, $ref_uri);
+ }
+ }
+
+ /**
+ * Get all cookies in the cookie jar as an array
+ *
+ * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings
+ * @return array|string
+ */
+ public function getAllCookies($ret_as = self::COOKIE_OBJECT)
+ {
+ $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as);
+ return $cookies;
+ }
+
+ /**
+ * Return an array of all cookies matching a specific request according to the request URI,
+ * whether session cookies should be sent or not, and the time to consider as "now" when
+ * checking cookie expiry time.
+ *
+ * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path)
+ * @param boolean $matchSessionCookies Whether to send session cookies
+ * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings
+ * @param int $now Override the current time when checking for expiry time
+ * @return array|string
+ */
+ public function getMatchingCookies($uri, $matchSessionCookies = true, $ret_as = self::COOKIE_OBJECT, $now = null)
+ {
+ if (is_string($uri)) {
+ if (($parsed = parse_url($uri)) !== false) {
+ $uri = Sabel_Http_Uri::fromArray($parsed);
+ }
+ }
+
+ if (!$uri instanceof Sabel_Http_Uri) {
+ $message = __METHOD__ . "() Invalid URI string or object passed.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ // First, reduce the array of cookies to only those matching domain and path
+ $cookies = $this->_matchDomain($uri->host);
+ $cookies = $this->_matchPath($cookies, $uri->path);
+ $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT);
+
+ // Next, run Cookie->match on all cookies to check secure, time and session mathcing
+ $ret = array();
+ foreach ($cookies as $cookie) {
+ if ($cookie->match($uri, $matchSessionCookies, $now)) {
+ $ret[] = $cookie;
+ }
+ }
+
+ // Now, use self::_flattenCookiesArray again - only to convert to the return format ;)
+ $ret = $this->_flattenCookiesArray($ret, $ret_as);
+
+ return $ret;
+ }
+
+ /**
+ * Get a specific cookie according to a URI and name
+ *
+ * @param Zend_Uri_Http|string $uri The uri (domain and path) to match
+ * @param string $cookie_name The cookie's name
+ * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings
+ * @return Zend_Http_Cookie|string
+ */
+ public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT)
+ {
+ if (is_string($uri)) {
+ if (($parsed = parse_url($uri)) !== false) {
+ $uri = Sabel_Http_Uri::fromArray($parsed);
+ }
+ }
+
+ if (!$uri instanceof Sabel_Http_Uri) {
+ $message = __METHOD__ . "() Invalid URI specified.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Get correct cookie path
+ $path = $uri->path;
+ $path = substr($path, 0, strrpos($path, "/"));
+ if (!$path) $path = "/";
+
+ if (isset($this->cookies[$uri->host][$path][$cookie_name])) {
+ $cookie = $this->cookies[$uri->host][$path][$cookie_name];
+
+ switch ($ret_as) {
+ case self::COOKIE_OBJECT:
+ return $cookie;
+ case self::COOKIE_STRING_ARRAY:
+ case self::COOKIE_STRING_CONCAT:
+ return $cookie->__toString();
+ default:
+ $message = "Invalid value passed for \$ret_as: {$ret_as}";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Helper function to recursivly flatten an array. Shoud be used when exporting the
+ * cookies array (or parts of it)
+ *
+ * @param Zend_Http_Cookie|array $ptr
+ * @param int $ret_as What value to return
+ * @return array|string
+ */
+ protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT)
+ {
+ if (is_array($ptr)) {
+ $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? "" : array());
+
+ foreach ($ptr as $item) {
+ if ($ret_as === self::COOKIE_STRING_CONCAT) {
+ $ret .= $this->_flattenCookiesArray($item, $ret_as);
+ } else {
+ $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as));
+ }
+ }
+
+ return $ret;
+ } elseif ($ptr instanceof Sabel_Http_Cookie) {
+ switch ($ret_as) {
+ case self::COOKIE_STRING_ARRAY:
+ return array($ptr->__toString());
+ break;
+ case self::COOKIE_STRING_CONCAT:
+ return $ptr->__toString();
+ break;
+ case self::COOKIE_OBJECT:
+ default:
+ return array($ptr);
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return a subset of the cookies array matching a specific domain
+ *
+ * @param string $domain
+ * @return array
+ */
+ protected function _matchDomain($domain)
+ {
+ $ret = array();
+
+ foreach (array_keys($this->cookies) as $cdom) {
+ if (Sabel_Http_Cookie::matchCookieDomain($cdom, $domain)) {
+ $ret[$cdom] = $this->cookies[$cdom];
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Return a subset of a domain-matching cookies that also match a specified path
+ *
+ * @param array $dom_array
+ * @param string $path
+ * @return array
+ */
+ protected function _matchPath($domains, $path)
+ {
+ $ret = array();
+
+ foreach ($domains as $dom => $paths_array) {
+ foreach (array_keys($paths_array) as $cpath) {
+ if (Sabel_Http_Cookie::matchCookiePath($cpath, $path)) {
+ if (! isset($ret[$dom])) {
+ $ret[$dom] = array();
+ }
+
+ $ret[$dom][$cpath] = $paths_array[$cpath];
+ }
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Create a new CookieJar object and automatically load into it all the
+ * cookies set in an Http_Response object. If $uri is set, it will be
+ * considered as the requested URI for setting default domain and path
+ * of the cookie.
+ *
+ * @param Zend_Http_Response $response HTTP Response object
+ * @param Zend_Uri_Http|string $uri The requested URI
+ * @return Zend_Http_CookieJar
+ * @todo Add the $uri functionality.
+ */
+ public static function fromResponse(Zend_Http_Response $response, $ref_uri)
+ {
+ $jar = new self();
+ $jar->addCookiesFromResponse($response, $ref_uri);
+
+ return $jar;
+ }
+
+ /**
+ * Required by Countable interface
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->_rawCookies);
+ }
+
+ /**
+ * Required by IteratorAggregate interface
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_rawCookies);
+ }
+
+ /**
+ * Tells if the jar is empty of any cookie
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return count($this) == 0;
+ }
+
+ /**
+ * Empties the cookieJar of any cookie
+ *
+ * @return Zend_Http_CookieJar
+ */
+ public function reset()
+ {
+ $this->cookies = $this->_rawCookies = array();
+
+ return $this;
+ }
+}
diff --git a/sabel/http/Response.php b/sabel/http/Response.php
new file mode 100755
index 0000000..1d19d38
--- /dev/null
+++ b/sabel/http/Response.php
@@ -0,0 +1,617 @@
+ "Continue",
+ 101 => "Switching Protocols",
+
+ // Success 2xx
+ 200 => "OK",
+ 201 => "Created",
+ 202 => "Accepted",
+ 203 => "Non-Authoritative Information",
+ 204 => "No Content",
+ 205 => "Reset Content",
+ 206 => "Partial Content",
+
+ // Redirection 3xx
+ 300 => "Multiple Choices",
+ 301 => "Moved Permanently",
+ 302 => "Found", // 1.1
+ 303 => "See Other",
+ 304 => "Not Modified",
+ 305 => "Use Proxy",
+ // 306 is deprecated but reserved
+ 307 => "Temporary Redirect",
+
+ // Client Error 4xx
+ 400 => "Bad Request",
+ 401 => "Unauthorized",
+ 402 => "Payment Required",
+ 403 => "Forbidden",
+ 404 => "Not Found",
+ 405 => "Method Not Allowed",
+ 406 => "Not Acceptable",
+ 407 => "Proxy Authentication Required",
+ 408 => "Request Timeout",
+ 409 => "Conflict",
+ 410 => "Gone",
+ 411 => "Length Required",
+ 412 => "Precondition Failed",
+ 413 => "Request Entity Too Large",
+ 414 => "Request-URI Too Long",
+ 415 => "Unsupported Media Type",
+ 416 => "Requested Range Not Satisfiable",
+ 417 => "Expectation Failed",
+
+ // Server Error 5xx
+ 500 => "Internal Server Error",
+ 501 => "Not Implemented",
+ 502 => "Bad Gateway",
+ 503 => "Service Unavailable",
+ 504 => "Gateway Timeout",
+ 505 => "HTTP Version Not Supported",
+ 509 => "Bandwidth Limit Exceeded"
+ );
+
+ /**
+ * The HTTP version (1.0, 1.1)
+ *
+ * @var string
+ */
+ protected $version;
+
+ /**
+ * The HTTP response code
+ *
+ * @var int
+ */
+ protected $code;
+
+ /**
+ * The HTTP response code as string
+ * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
+ *
+ * @var string
+ */
+ protected $message;
+
+ /**
+ * The HTTP response headers array
+ *
+ * @var array
+ */
+ protected $headers = array();
+
+ /**
+ * The HTTP response body
+ *
+ * @var string
+ */
+ protected $body;
+
+ /**
+ * HTTP response constructor
+ *
+ * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
+ * response string and create a new Zend_Http_Response object.
+ *
+ * NOTE: The constructor no longer accepts nulls or empty values for the code and
+ * headers and will throw an exception if the passed values do not form a valid HTTP
+ * responses.
+ *
+ * If no message is passed, the message will be guessed according to the response code.
+ *
+ * @param int $code Response code (200, 404, ...)
+ * @param array $headers Headers array
+ * @param string $body Response body
+ * @param string $version HTTP version
+ * @param string $message Response code as text
+ * @throws Zend_Http_Exception
+ */
+ public function __construct($code, $headers, $body = null, $version = "1.1", $message = null)
+ {
+ // Make sure the response code is valid and set it
+ if (self::responseCodeAsText($code) === null) {
+ $message = __METHOD__ . "() {$code} is not a valid HTTP response code.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ $this->code = $code;
+
+ // Make sure we got valid headers and set them
+ if (!is_array($headers)) {
+ $message = __METHOD__ . "() No valid headers were passed.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ foreach ($headers as $name => $value) {
+ if (is_int($name)) {
+ list($name, $value) = explode(": ", $value, 1);
+ }
+
+ $this->headers[ucwords(strtolower($name))] = $value;
+ }
+
+ // Set the body
+ $this->body = $body;
+
+ // Set the HTTP version
+ if (!preg_match('|^\d\.\d$|', $version)) {
+ $message = __METHOD__ . "() Invalid HTTP response version: {$version}";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $this->version = $version;
+
+ // If we got the response message, set it. Else, set it according to
+ // the response code
+ if (is_string($message)) {
+ $this->message = $message;
+ } else {
+ $this->message = self::responseCodeAsText($code);
+ }
+ }
+
+ /**
+ * Check whether the response is an error
+ *
+ * @return boolean
+ */
+ public function isError()
+ {
+ $restype = floor($this->code / 100);
+ return ($restype == 4 || $restype == 5);
+ }
+
+ /**
+ * Check whether the response in successful
+ *
+ * @return boolean
+ */
+ public function isSuccessful()
+ {
+ $restype = floor($this->code / 100);
+ return ($restype == 2 || $restype == 1);
+ }
+
+ /**
+ * Check whether the response is a redirection
+ *
+ * @return boolean
+ */
+ public function isRedirect()
+ {
+ $restype = floor($this->code / 100);
+ return ($restype == 3);
+ }
+
+ /**
+ * Get the response body as string
+ *
+ * This method returns the body of the HTTP response (the content), as it
+ * should be in it's readable version - that is, after decoding it (if it
+ * was decoded), deflating it (if it was gzip compressed), etc.
+ *
+ * If you want to get the raw body (as transfered on wire) use
+ * $this->getRawBody() instead.
+ *
+ * @return string
+ */
+ public function getBody()
+ {
+ $body = "";
+
+ // Decode the body if it was transfer-encoded
+ switch (strtolower($this->getHeader("transfer-encoding"))) {
+ // Handle chunked body
+ case "chunked":
+ $body = self::decodeChunkedBody($this->body);
+ break;
+
+ // No transfer encoding, or unknown encoding extension:
+ // return body as is
+ default:
+ $body = $this->body;
+ break;
+ }
+
+ // Decode any content-encoding (gzip or deflate) if needed
+ switch (strtolower($this->getHeader("content-encoding"))) {
+ // Handle gzip encoding
+ case "gzip":
+ $body = self::decodeGzip($body);
+ break;
+
+ // Handle deflate encoding
+ case "eflate":
+ $body = self::decodeDeflate($body);
+ break;
+ }
+
+ return $body;
+ }
+
+ public function getContent()
+ {
+ return $this->getBody();
+ }
+
+ public function getContents()
+ {
+ return $this->getBody();
+ }
+
+ /**
+ * Get the raw response body (as transfered "on wire") as string
+ *
+ * If the body is encoded (with Transfer-Encoding, not content-encoding -
+ * IE "chunked" body), gzip compressed, etc. it will not be decoded.
+ *
+ * @return string
+ */
+ public function getRawBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * Get the HTTP version of the response
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Get the HTTP response status code
+ *
+ * @return int
+ */
+ public function getStatus()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Return a message describing the HTTP response code
+ * (Eg. "OK", "Not Found", "Moved Permanently")
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Get the response headers
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Get a specific header as string, or null if it is not set
+ *
+ * @param string$header
+ * @return string|array|null
+ */
+ public function getHeader($header)
+ {
+ $header = ucwords(strtolower($header));
+ if (!is_string($header) || !isset($this->headers[$header])) return null;
+
+ return $this->headers[$header];
+ }
+
+ /**
+ * Get all headers as string
+ *
+ * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
+ * @param string $br Line breaks (eg. "\n", "\r\n", " ")
+ * @return string
+ */
+ public function getHeadersAsString($status_line = true, $br = "\n")
+ {
+ $str = "";
+
+ if ($status_line) {
+ $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
+ }
+
+ // Iterate over the headers and stringify them
+ foreach ($this->headers as $name => $value) {
+ if (is_string($value)) {
+ $str .= "{$name}: {$value}{$br}";
+ } elseif (is_array($value)) {
+ foreach ($value as $subval) {
+ $str .= "{$name}: {$subval}{$br}";
+ }
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Get the entire response as string
+ *
+ * @param string $br Line breaks (eg. "\n", "\r\n", " ")
+ * @return string
+ */
+ public function asString($br = "\n")
+ {
+ return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
+ }
+
+ /**
+ * Implements magic __toString()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->asString();
+ }
+
+ /**
+ * A convenience function that returns a text representation of
+ * HTTP response codes. Returns 'Unknown' for unknown codes.
+ * Returns array of all codes, if $code is not specified.
+ *
+ * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
+ * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
+ *
+ * @param int $code HTTP response code
+ * @param boolean $http11 Use HTTP version 1.1
+ * @return string
+ */
+ public static function responseCodeAsText($code = null, $http11 = true)
+ {
+ $messages = self::$messages;
+ if (!$http11) $messages[302] = "Moved Temporarily";
+
+ if ($code === null) {
+ return $messages;
+ } elseif (isset($messages[$code])) {
+ return $messages[$code];
+ } else {
+ return "Unknown";
+ }
+ }
+
+ /**
+ * Extract the response code from a response string
+ *
+ * @param string $response_str
+ * @return int
+ */
+ public static function extractCode($response_str)
+ {
+ preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $matches);
+ return (isset($matches[1])) ? (int)$matches[1] : false;
+ }
+
+ /**
+ * Extract the HTTP message from a response
+ *
+ * @param string $response_str
+ * @return string
+ */
+ public static function extractMessage($response_str)
+ {
+ preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $matches);
+ return (isset($matches[1])) ? $matches[1] : false;
+ }
+
+ /**
+ * Extract the HTTP version from a response
+ *
+ * @param string $response_str
+ * @return string
+ */
+ public static function extractVersion($response_str)
+ {
+ preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $matches);
+ return (isset($matches[1])) ? $matches[1] : false;
+ }
+
+ /**
+ * Extract the headers from a response string
+ *
+ * @param string $response_str
+ * @return array
+ */
+ public static function extractHeaders($response_str)
+ {
+ $headers = array();
+
+ // First, split body and headers
+ $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
+ if (!$parts[0]) return $headers;
+
+ // Split headers part to lines
+ $lines = explode("\n", $parts[0]);
+ unset($parts);
+
+ $last_header = null;
+ foreach($lines as $line) {
+ $line = trim($line, "\r\n");
+ if ($line == "") break;
+
+ if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) {
+ unset($last_header);
+ $h_name = strtolower($m[1]);
+ $h_value = $m[2];
+
+ if (isset($headers[$h_name])) {
+ if (! is_array($headers[$h_name])) {
+ $headers[$h_name] = array($headers[$h_name]);
+ }
+
+ $headers[$h_name][] = $h_value;
+ } else {
+ $headers[$h_name] = $h_value;
+ }
+
+ $last_header = $h_name;
+ } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
+ if (is_array($headers[$last_header])) {
+ end($headers[$last_header]);
+ $last_header_key = key($headers[$last_header]);
+ $headers[$last_header][$last_header_key] .= $m[1];
+ } else {
+ $headers[$last_header] .= $m[1];
+ }
+ }
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Extract the body from a response string
+ *
+ * @param string $response_str
+ * @return string
+ */
+ public static function extractBody($response_str)
+ {
+ $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
+ return (isset($parts[1])) ? $parts[1] : "";
+ }
+
+ /**
+ * Decode a "chunked" transfer-encoded body and return the decoded text
+ *
+ * @param string $body
+ * @return string
+ */
+ public static function decodeChunkedBody($body)
+ {
+ $decBody = "";
+ $mbIntEnc = null;
+
+ // If mbstring overloads substr and strlen functions, we have to
+ // override it's internal encoding
+ if (function_exists("mb_internal_encoding") && ((int) ini_get("mbstring.func_overload")) & 2) {
+ $mbIntEnc = mb_internal_encoding();
+ mb_internal_encoding("ASCII");
+ }
+
+ while (trim($body)) {
+ if (!preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
+ $message = __METHOD__ . "() Error parsing body - doesn't seem to be a chunked message.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $length = hexdec(trim($m[1]));
+ $cut = strlen($m[0]);
+ $decBody .= substr($body, $cut, $length);
+ $body = substr($body, $cut + $length + 2);
+ }
+
+ if ($mbIntEnc !== null) {
+ mb_internal_encoding($mbIntEnc);
+ }
+
+ return $decBody;
+ }
+
+ /**
+ * Decode a gzip encoded message (when Content-encoding = gzip)
+ *
+ * Currently requires PHP with zlib support
+ *
+ * @param string $body
+ * @return string
+ */
+ public static function decodeGzip($body)
+ {
+ if (!function_exists("gzinflate")) {
+ $message = __METHOD__ . "() zlib extension is required in order to decode 'gzip' encoding.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ return gzinflate(substr($body, 10));
+ }
+
+ /**
+ * Decode a zlib deflated message (when Content-encoding = deflate)
+ *
+ * Currently requires PHP with zlib support
+ *
+ * @param string $body
+ * @return string
+ */
+ public static function decodeDeflate($body)
+ {
+ if (!function_exists("gzuncompress")) {
+ $message = __METHOD__ . "() zlib extension is required in order to decode 'deflate' encoding.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ /**
+ * Some servers (IIS ?) send a broken deflate response, without the
+ * RFC-required zlib header.
+ *
+ * We try to detect the zlib header, and if it does not exsit we
+ * teat the body is plain DEFLATE content.
+ *
+ * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
+ *
+ * @link http://framework.zend.com/issues/browse/ZF-6040
+ */
+
+ $zlibHeader = unpack("n", substr($body, 0, 2));
+
+ if ($zlibHeader[1] % 31 == 0) {
+ return gzuncompress($body);
+ } else {
+ return gzinflate($body);
+ }
+ }
+
+ /**
+ * Create a new Zend_Http_Response object from a string
+ *
+ * @param string $response_str
+ * @return Zend_Http_Response
+ */
+ public static function fromString($response_str)
+ {
+ $code = self::extractCode($response_str);
+ $headers = self::extractHeaders($response_str);
+ $body = self::extractBody($response_str);
+ $version = self::extractVersion($response_str);
+ $message = self::extractMessage($response_str);
+
+ return new self($code, $headers, $body, $version, $message);
+ }
+}
diff --git a/sabel/http/Uri.php b/sabel/http/Uri.php
new file mode 100755
index 0000000..e14d572
--- /dev/null
+++ b/sabel/http/Uri.php
@@ -0,0 +1,23 @@
+addValues($values);
+
+ return $self;
+ }
+
+ public function __toString()
+ {
+ $pass = (strlen($this->pass) > 0) ? ":{$this->pass}" : "";
+ $auth = (strlen($this->user) > 0) ? "{$this->user}{$pass}@" : "";
+ $port = (strlen($this->port) > 0) ? ":{$this->port}" : "";
+ $query = (strlen($this->query) > 0) ? "?{$this->query}" : "";
+ $frag = (strlen($this->fragment) > 0) ? "#{$this->fragment}" : "";
+
+ return $this->scheme . "://" . $auth . $this->host . $port . $this->path . $query . $frag;
+ }
+}
diff --git a/sabel/http/client/adapter/Curl.php b/sabel/http/client/adapter/Curl.php
new file mode 100755
index 0000000..5d10635
--- /dev/null
+++ b/sabel/http/client/adapter/Curl.php
@@ -0,0 +1,404 @@
+setCurlOption(CURLOPT_PROXYUSERPWD, $config["proxy_user"] . ":" . $config["proxy_pass"]);
+ unset($config["proxy_user"], $config["proxy_pass"]);
+ }
+
+ foreach ($config as $k => $v) {
+ $option = strtolower($k);
+ switch($option) {
+ case "proxy_host":
+ $this->setCurlOption(CURLOPT_PROXY, $v);
+ break;
+ case "proxy_port":
+ $this->setCurlOption(CURLOPT_PROXYPORT, $v);
+ break;
+ default:
+ $this->_config[$option] = $v;
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the array of all configuration options which
+ * are not simply passed immediately to CURL extension.
+ *
+ * @return array
+ */
+ public function getConfig()
+ {
+ return $this->_config;
+ }
+
+ /**
+ * Direct setter for cURL adapter related options.
+ *
+ * @param string|int $option
+ * @param mixed $value
+ * @return Zend_Http_Adapter_Curl
+ */
+ public function setCurlOption($option, $value)
+ {
+ if (!isset($this->_config["curloptions"])) {
+ $this->_config["curloptions"] = array();
+ }
+
+ $this->_config["curloptions"][$option] = $value;
+ return $this;
+ }
+
+ /**
+ * Initialize curl
+ *
+ * @param string $host
+ * @param int $port
+ * @param boolean $secure
+ * @return void
+ * @throws Zend_Http_Client_Adapter_Exception if unable to connect
+ */
+ public function connect($host, $port = 80, $secure = false)
+ {
+ // If we're already connected, disconnect first
+ if ($this->_curl) {
+ $this->close();
+ }
+
+ // If we are connected to a different server or port, disconnect first
+ if ($this->_curl
+ && is_array($this->_connected_to)
+ && ($this->_connected_to[0] != $host
+ || $this->_connected_to[1] != $port)
+ ) {
+ $this->close();
+ }
+
+ // Do the actual connection
+ $this->_curl = curl_init();
+ if ($port != 80) {
+ curl_setopt($this->_curl, CURLOPT_PORT, intval($port));
+ }
+
+ // Set timeout
+ curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config["timeout"]);
+
+ // Set Max redirects
+ curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config["maxredirects"]);
+
+ if (!$this->_curl) {
+ $this->close();
+
+ $message = __METHOD__ . "() Unable to Connect to {$host}:{$port}.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if ($secure !== false) {
+ // Behave the same like Zend_Http_Adapter_Socket on SSL options.
+ if (isset($this->_config["sslcert"])) {
+ curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config["sslcert"]);
+ }
+
+ if (isset($this->_config["sslpassphrase"])) {
+ curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config["sslpassphrase"]);
+ }
+ }
+
+ // Update connected_to
+ $this->_connected_to = array($host, $port);
+ }
+
+ /**
+ * Send request to the remote server
+ *
+ * @param string $method
+ * @param Zend_Uri_Http $uri
+ * @param float $http_ver
+ * @param array $headers
+ * @param string $body
+ * @return string $request
+ * @throws Zend_Http_Client_Adapter_Exception If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option
+ */
+ public function write($method, $uri, $http_ver = "1.1", $headers = array(), $body = "")
+ {
+ // Make sure we're properly connected
+ if (!$this->_curl) {
+ $message = __METHOD__ . "() Trying to write but we are not connected.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ if ($this->_connected_to[0] != $uri->host || $this->_connected_to[1] != $uri->port) {
+ $message = __METHOD__ . "() Trying to write but we are connected to the wrong host.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // set URL
+ curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString());
+
+ // ensure correct curl call
+ $curlValue = true;
+ switch ($method) {
+ case Sabel_Http_Client::GET:
+ $curlMethod = CURLOPT_HTTPGET;
+ break;
+
+ case Sabel_Http_Client::POST:
+ $curlMethod = CURLOPT_POST;
+ break;
+
+ case Sabel_Http_Client::PUT:
+ // There are two different types of PUT request, either a Raw Data string has been set
+ // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used.
+ if (isset($this->_config["curloptions"][CURLOPT_INFILE])) {
+ if (!isset($this->_config["curloptions"][CURLOPT_INFILESIZE])) {
+ $message = __METHOD__ . "() Cannot set a file-handle for cURL option CURLOPT_INFILE "
+ . "without also set its size in CURLOPT_INFILESIZE.";
+
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Now we will probably already have Content-Length set, so that we have to delete it
+ // from $headers at this point:
+ foreach ($headers AS $k => $header) {
+ if (stristr($header, "Content-Length:") !== false) {
+ unset($headers[$k]);
+ }
+ }
+
+ $curlMethod = CURLOPT_PUT;
+ } else {
+ $curlMethod = CURLOPT_CUSTOMREQUEST;
+ $curlValue = "PUT";
+ }
+ break;
+
+ case Sabel_Http_Client::DELETE:
+ $curlMethod = CURLOPT_CUSTOMREQUEST;
+ $curlValue = "DELETE";
+ break;
+
+ case Sabel_Http_Client::OPTIONS:
+ $curlMethod = CURLOPT_CUSTOMREQUEST;
+ $curlValue = "OPTIONS";
+ break;
+
+ case Sabel_Http_Client::TRACE:
+ $curlMethod = CURLOPT_CUSTOMREQUEST;
+ $curlValue = "TRACE";
+ break;
+
+ default:
+ // For now, through an exception for unsupported request methods
+ $message = __METHOD__ . "() Method currently not supported.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // get http version to use
+ $curlHttp = ($http_ver = 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
+
+ // mark as HTTP request and set HTTP method
+ curl_setopt($this->_curl, $curlHttp, true);
+ curl_setopt($this->_curl, $curlMethod, $curlValue);
+
+ // ensure headers are also returned
+ curl_setopt($this->_curl, CURLOPT_HEADER, true);
+
+ // ensure actual response is returned
+ curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
+
+ // set additional headers
+ $headers["Accept"] = "";
+ curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
+
+ /**
+ * Make sure POSTFIELDS is set after $curlMethod is set:
+ * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161
+ */
+ if ($method == Sabel_Http_Client::POST) {
+ curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
+ } elseif ($curlMethod == CURLOPT_PUT) {
+ // this covers a PUT by file-handle:
+ // Make the setting of this options explicit (rather than setting it through the loop following a bit lower)
+ // to group common functionality together.
+ curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config["curloptions"][CURLOPT_INFILE]);
+ curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config["curloptions"][CURLOPT_INFILESIZE]);
+ unset($this->_config["curloptions"][CURLOPT_INFILE]);
+ unset($this->_config["curloptions"][CURLOPT_INFILESIZE]);
+ } elseif ($method == Sabel_Http_Client::PUT) {
+ // This is a PUT by a setRawData string, not by file-handle
+ curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
+ }
+
+ // set additional curl options
+ if (isset($this->_config["curloptions"])) {
+ foreach ((array)$this->_config["curloptions"] as $k => $v) {
+ if (!in_array($k, $this->_invalidOverwritableCurlOptions)) {
+ if (curl_setopt($this->_curl, $k, $v) == false) {
+ $message = __METHOD__ . "() Unknown or erroreous cURL option '%s' set.";
+ throw new Sabel_Exception_Runtime(sprintf($message, $k));
+ }
+ }
+ }
+ }
+
+ // send the request
+ $this->_response = curl_exec($this->_curl);
+
+ $request = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT);
+ $request .= $body;
+
+ if (empty($this->_response)) {
+ $message = __METHOD__ . "() Error in cURL request: " . curl_error($this->_curl);
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again
+ if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) {
+ $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", "", $this->_response);
+ }
+
+ // Eliminate multiple HTTP responses.
+ do {
+ $parts = preg_split('|(?:\r?\n){2}|m', $this->_response, 2);
+ $again = false;
+
+ if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) {
+ $this->_response = $parts[1];
+ $again = true;
+ }
+ } while ($again);
+
+ // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string:
+ if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) {
+ $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", "", $this->_response);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Return read response from server
+ *
+ * @return string
+ */
+ public function read()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Close the connection to the server
+ *
+ */
+ public function close()
+ {
+ if (is_resource($this->_curl)) {
+ curl_close($this->_curl);
+ }
+
+ $this->_curl = null;
+ $this->_connected_to = array(null, null);
+ }
+
+ /**
+ * Get cUrl Handle
+ *
+ * @return resource
+ */
+ public function getHandle()
+ {
+ return $this->_curl;
+ }
+}
diff --git a/sabel/http/client/adapter/Interface.php b/sabel/http/client/adapter/Interface.php
new file mode 100755
index 0000000..ac41d9c
--- /dev/null
+++ b/sabel/http/client/adapter/Interface.php
@@ -0,0 +1,59 @@
+ "ssl",
+ "sslcert" => null,
+ "sslpassphrase" => null,
+ "proxy_host" => "",
+ "proxy_port" => 8080,
+ "proxy_user" => "",
+ "proxy_pass" => "",
+ "proxy_auth" => Sabel_Http_Client::AUTH_BASIC,
+ "persistent" => false
+ );
+
+ /**
+ * Whether HTTPS CONNECT was already negotiated with the proxy or not
+ *
+ * @var boolean
+ */
+ protected $negotiated = false;
+
+ /**
+ * Connect to the remote server
+ *
+ * Will try to connect to the proxy server. If no proxy was set, will
+ * fall back to the target server (behave like regular Socket adapter)
+ *
+ * @param string $host
+ * @param int $port
+ * @param boolean $secure
+ */
+ public function connect($host, $port = 80, $secure = false)
+ {
+ // If no proxy is set, fall back to Socket adapter
+ if (!$this->config["proxy_host"]) {
+ return parent::connect($host, $port, $secure);
+ }
+
+ // Connect (a non-secure connection) to the proxy server
+ return parent::connect($this->config["proxy_host"], $this->config["proxy_port"], false);
+ }
+
+ /**
+ * Send request to the proxy server
+ *
+ * @param string $method
+ * @param Zend_Uri_Http $uri
+ * @param string $http_ver
+ * @param array $headers
+ * @param string $body
+ * @return string Request as string
+ */
+ public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
+ {
+ // If no proxy is set, fall back to default Socket adapter
+ if (!$this->config["proxy_host"]) {
+ return parent::write($method, $uri, $http_ver, $headers, $body);
+ }
+
+ // Make sure we're properly connected
+ if (!$this->socket) {
+ $message = __METHOD__ . "() Trying to write but we are not connected.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $host = $this->config["proxy_host"];
+ $port = $this->config["proxy_port"];
+
+ if ($this->connected_to[0] != "tcp://{$host}" || $this->connected_to[1] != $port) {
+ $message = __METHOD__ . "() Trying to write but we are connected to the wrong proxy server.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Add Proxy-Authorization header
+ if ($this->config["proxy_user"] && !isset($headers["proxy-authorization"])) {
+ $headers["proxy-authorization"] = Sabel_Http_Client::encodeAuthHeader(
+ $this->config["proxy_user"], $this->config["proxy_pass"], $this->config["proxy_auth"]
+ );
+ }
+
+ // if we are proxying HTTPS, preform CONNECT handshake with the proxy
+ if ($uri->scheme === "https" && (!$this->negotiated)) {
+ $this->connectHandshake($uri->host, $uri->port, $http_ver, $headers);
+ $this->negotiated = true;
+ }
+
+ // Save request method for later
+ $this->method = $method;
+
+ // Build request headers
+ if ($this->negotiated) {
+ $path = $uri->path;
+ if ($uri->query) {
+ $path .= "?" . $uri->getQuery();
+ }
+
+ $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
+ } else {
+ $request = "{$method} {$uri} HTTP/{$http_ver}\r\n";
+ }
+
+ // Add all headers to the request string
+ foreach ($headers as $k => $v) {
+ if (is_string($k)) $v = "{$k}: {$v}";
+ $request .= "$v\r\n";
+ }
+
+ // Add the request body
+ $request .= "\r\n" . $body;
+
+ // Send the request
+ if (!@fwrite($this->socket, $request)) {
+ $message = __METHOD__ . "() Error writing request to proxy server.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Preform handshaking with HTTPS proxy using CONNECT method
+ *
+ * @param string $host
+ * @param integer $port
+ * @param string $http_ver
+ * @param array $headers
+ */
+ protected function connectHandshake($host, $port = 443, $http_ver = "1.1", array &$headers = array())
+ {
+ $request = "CONNECT {$host}:{$port} HTTP/{$http_ver}\r\n"
+ . "Host: " . $this->config["proxy_host"] . "\r\n";
+
+ // Add the user-agent header
+ if (isset($this->config["useragent"])) {
+ $request .= "User-agent: " . $this->config["useragent"] . "\r\n";
+ }
+
+ // If the proxy-authorization header is set, send it to proxy but remove
+ // it from headers sent to target host
+ if (isset($headers["proxy-authorization"])) {
+ $request .= "Proxy-authorization: " . $headers["proxy-authorization"] . "\r\n";
+ unset($headers["proxy-authorization"]);
+ }
+
+ $request .= "\r\n";
+
+ // Send the request
+ if (!@fwrite($this->socket, $request)) {
+ $message = __METHOD__ . "() Error writing request to proxy server.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Read response headers only
+ $response = "";
+ $gotStatus = false;
+
+ while ($line = @fgets($this->socket)) {
+ $gotStatus = $gotStatus || (strpos($line, "HTTP") !== false);
+
+ if ($gotStatus) {
+ $response .= $line;
+ if (!chop($line)) break;
+ }
+ }
+
+ // Check that the response from the proxy is 200
+ if (Sabel_Http_Response::extractCode($response) != 200) {
+ $message = __METHOD__ . "() Unable to connect to HTTPS proxy. Server response: {$response}.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // If all is good, switch socket to secure mode. We have to fall back
+ // through the different modes
+ $modes = array(
+ STREAM_CRYPTO_METHOD_TLS_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
+ STREAM_CRYPTO_METHOD_SSLv2_CLIENT
+ );
+
+ $success = false;
+ foreach($modes as $mode) {
+ $success = stream_socket_enable_crypto($this->socket, true, $mode);
+ if ($success) break;
+ }
+
+ if (!$success) {
+ $message = __METHOD__ . "() Unable to connect to HTTPS server through "
+ . "proxy: could not negotiate secure connection.";
+
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ /**
+ * Close the connection to the server
+ *
+ */
+ public function close()
+ {
+ parent::close();
+ $this->negotiated = false;
+ }
+
+ /**
+ * Destructor: make sure the socket is disconnected
+ *
+ */
+ public function __destruct()
+ {
+ if ($this->socket) {
+ $this->close();
+ }
+ }
+}
diff --git a/sabel/http/client/adapter/Socket.php b/sabel/http/client/adapter/Socket.php
new file mode 100755
index 0000000..ed30100
--- /dev/null
+++ b/sabel/http/client/adapter/Socket.php
@@ -0,0 +1,418 @@
+ false,
+ "ssltransport" => "ssl",
+ "sslcert" => null,
+ "sslpassphrase" => null
+ );
+
+ /**
+ * Request method - will be set by write() and might be used by read()
+ *
+ * @var string
+ */
+ protected $method = null;
+
+ /**
+ * Stream context
+ *
+ * @var resource
+ */
+ protected $_context = null;
+
+ /**
+ * Adapter constructor, currently empty. Config is set using setConfig()
+ *
+ */
+ public function __construct()
+ {
+
+ }
+
+ /**
+ * Set the configuration array for the adapter
+ *
+ * @param Zend_Config | array $config
+ */
+ public function setConfig(array $config = array())
+ {
+ foreach ($config as $k => $v) {
+ $this->config[strtolower($k)] = $v;
+ }
+ }
+
+ /**
+ * Retrieve the array of all configuration options
+ *
+ * @return array
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Set the stream context for the TCP connection to the server
+ *
+ * Can accept either a pre-existing stream context resource, or an array
+ * of stream options, similar to the options array passed to the
+ * stream_context_create() PHP function. In such case a new stream context
+ * will be created using the passed options.
+ *
+ * @since Zend Framework 1.9
+ *
+ * @param mixed $context Stream context or array of context options
+ * @return Zend_Http_Client_Adapter_Socket
+ */
+ public function setStreamContext($context)
+ {
+ if (is_resource($context) && get_resource_type($context) == "stream-context") {
+ $this->_context = $context;
+ } elseif (is_array($context)) {
+ $this->_context = stream_context_create($context);
+ } else {
+ $message = __METHOD__ . "() Expecting either a stream context resource or array.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the stream context for the TCP connection to the server.
+ *
+ * If no stream context is set, will create a default one.
+ *
+ * @return resource
+ */
+ public function getStreamContext()
+ {
+ if (! $this->_context) {
+ $this->_context = stream_context_create();
+ }
+
+ return $this->_context;
+ }
+
+ /**
+ * Connect to the remote server
+ *
+ * @param string $host
+ * @param int $port
+ * @param boolean $secure
+ */
+ public function connect($host, $port = 80, $secure = false)
+ {
+ // If the URI should be accessed via SSL, prepend the Hostname with ssl://
+ $host = ($secure ? $this->config["ssltransport"] : "tcp") . "://" . $host;
+
+ // If we are connected to the wrong host, disconnect first
+ if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) {
+ if (is_resource($this->socket)) $this->close();
+ }
+
+ // Now, if we are not connected, connect
+ if (!is_resource($this->socket) || !$this->config["keepalive"]) {
+ $context = $this->getStreamContext();
+ if ($secure) {
+ if ($this->config["sslcert"] !== null) {
+ if (!stream_context_set_option($context, "ssl", "local_cert", $this->config["sslcert"])) {
+ $message = __METHOD__ . "() Unable to set sslcert option.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ if ($this->config["sslpassphrase"] !== null) {
+ if (!stream_context_set_option($context, "ssl", "passphrase", $this->config["sslpassphrase"])) {
+ $message = __METHOD__ . "() Unable to set sslpassphrase option.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+ }
+
+ $flags = STREAM_CLIENT_CONNECT;
+ if ($this->config["persistent"]) $flags |= STREAM_CLIENT_PERSISTENT;
+
+ $timeout = (int)$this->config["timeout"];
+
+ $this->socket = @stream_socket_client(
+ $host . ":" . $port, $errno, $errstr, $timeout, $flags, $context
+ );
+
+ if (!$this->socket) {
+ $this->close();
+
+ $message = __METHOD__ . "() Unable to Connect to {$host}: {$port}. Error #{$errno}: {$errstr}";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Set the stream timeout
+ if (!stream_set_timeout($this->socket, $timeout)) {
+ $method = __METHOD__ . "() Unable to set the connection timeout.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Update connected_to
+ $this->connected_to = array($host, $port);
+ }
+ }
+
+ /**
+ * Send request to the remote server
+ *
+ * @param string $method
+ * @param Zend_Uri_Http $uri
+ * @param string $http_ver
+ * @param array $headers
+ * @param string $body
+ * @return string Request as string
+ */
+ public function write($method, $uri, $http_ver = "1.1", $headers = array(), $body = "")
+ {
+ // Make sure we're properly connected
+ if (!$this->socket) {
+ $message = __METHOD__ . "() Trying to write but we are not connected.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $host = $uri->host;
+ $host = (strtolower($uri->scheme) == "https" ? $this->config["ssltransport"] : "tcp") . "://" . $host;
+
+ if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->port) {
+ $message = __METHOD__ . "() Trying to write but we are connected to the wrong host.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Save request method for later
+ $this->method = $method;
+
+ // Build request headers
+ $path = $uri->path;
+ if ($uri->query) $path .= "?" . $uri->query;
+
+ $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
+ foreach ($headers as $k => $v) {
+ if (is_string($k)) $v = ucfirst($k) . ": {$v}";
+ $request .= "{$v}\r\n";
+ }
+
+ // Add the request body
+ $request .= "\r\n" . $body;
+
+ // Send the request
+ if (!@fwrite($this->socket, $request)) {
+ $message = __METHOD__ . "() Error writing request to server.";
+ throw new Sabel_Exception_Runtime('Error writing request to server');
+ }
+
+ return $request;
+ }
+
+ /**
+ * Read response from server
+ *
+ * @return string
+ */
+ public function read()
+ {
+ // First, read headers only
+ $response = "";
+ $gotStatus = false;
+
+ while (($line = @fgets($this->socket)) !== false) {
+ $gotStatus = $gotStatus || (strpos($line, "HTTP") !== false);
+ if ($gotStatus) {
+ $response .= $line;
+ if (rtrim($line) === "") break;
+ }
+ }
+
+ $this->_checkSocketReadTimeout();
+
+ $statusCode = Sabel_Http_Response::extractCode($response);
+
+ // Handle 100 and 101 responses internally by restarting the read again
+ if ($statusCode == 100 || $statusCode == 101) {
+ return $this->read();
+ }
+
+ // Check headers to see what kind of connection / transfer encoding we have
+ $headers = Sabel_Http_Response::extractHeaders($response);
+
+ /**
+ * Responses to HEAD requests and 204 or 304 responses are not expected
+ * to have a body - stop reading here
+ */
+ if ($statusCode == 304 || $statusCode == 204 || $this->method === Sabel_Http_Client::HEAD) {
+ // Close the connection if requested to do so by the server
+ if (isset($headers["connection"]) && $headers["connection"] === "close") {
+ $this->close();
+ }
+
+ return $response;
+ }
+
+ // If we got a 'transfer-encoding: chunked' header
+ if (isset($headers["transfer-encoding"])) {
+ if (strtolower($headers["transfer-encoding"]) === "chunked") {
+ do {
+ $line = @fgets($this->socket);
+ $this->_checkSocketReadTimeout();
+
+ $chunk = $line;
+
+ // Figure out the next chunk size
+ $chunksize = trim($line);
+ if (!ctype_xdigit($chunksize)) {
+ $this->close();
+
+ $message = __METHOD__ . "() Unable to read chunk body.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ // Convert the hexadecimal value to plain integer
+ $chunksize = hexdec($chunksize);
+
+ // Read next chunk
+ $read_to = ftell($this->socket) + $chunksize;
+
+ do {
+ $current_pos = ftell($this->socket);
+ if ($current_pos >= $read_to) break;
+
+ $line = @fread($this->socket, $read_to - $current_pos);
+ if ($line === false || strlen($line) === 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ } else {
+ $chunk .= $line;
+ }
+ } while (!feof($this->socket));
+
+ $chunk .= @fgets($this->socket);
+ $this->_checkSocketReadTimeout();
+ $response .= $chunk;
+ } while ($chunksize > 0);
+ } else {
+ $this->close();
+
+ $message = __METHOD__ . "() Cannot handle '{$headers['transfer-encoding']}' transfer encoding. ";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ } elseif (isset($headers["content-length"])) {
+ $current_pos = ftell($this->socket);
+ $chunk = "";
+
+ for ($read_to = $current_pos + $headers["content-length"]; $read_to > $current_pos; $current_pos = ftell($this->socket)) {
+ $chunk = @fread($this->socket, $read_to - $current_pos);
+ if ($chunk === false || strlen($chunk) === 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ }
+
+ $response .= $chunk;
+
+ // Break if the connection ended prematurely
+ if (feof($this->socket)) break;
+ }
+ } else {
+ do {
+ $buff = @fread($this->socket, 8192);
+ if ($buff === false || strlen($buff) === 0) {
+ $this->_checkSocketReadTimeout();
+ break;
+ } else {
+ $response .= $buff;
+ }
+ } while (feof($this->socket) === false);
+
+ $this->close();
+ }
+
+ // Close the connection if requested to do so by the server
+ if (isset($headers["connection"]) && $headers["connection"] === "close") {
+ $this->close();
+ }
+
+ return $response;
+ }
+
+ /**
+ * Close the connection to the server
+ *
+ */
+ public function close()
+ {
+ if (is_resource($this->socket)) {
+ @fclose($this->socket);
+ }
+
+ $this->socket = null;
+ $this->connected_to = array(null, null);
+ }
+
+ /**
+ * Check if the socket has timed out - if so close connection and throw
+ * an exception
+ *
+ * @throws Zend_Http_Client_Adapter_Exception with READ_TIMEOUT code
+ */
+ protected function _checkSocketReadTimeout()
+ {
+ if ($this->socket) {
+ $info = stream_get_meta_data($this->socket);
+ $timedout = $info["timed_out"];
+
+ if ($timedout) {
+ $this->close();
+
+ $message = __METHOD__ . "() Read timed out after {$this->config['timeout']} seconds";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+ }
+
+ /**
+ * Destructor: make sure the socket is disconnected
+ *
+ * If we are in persistent TCP mode, will not close the connection
+ *
+ */
+ public function __destruct()
+ {
+ if (!$this->config["persistent"] && $this->socket) {
+ $this->close();
+ }
+ }
+}
diff --git a/sabel/http/client/adapter/Test.php b/sabel/http/client/adapter/Test.php
new file mode 100755
index 0000000..f9be1a0
--- /dev/null
+++ b/sabel/http/client/adapter/Test.php
@@ -0,0 +1,176 @@
+ $v) {
+ $this->config[strtolower($k)] = $v;
+ }
+ }
+
+ /**
+ * Connect to the remote server
+ *
+ * @param string $host
+ * @param int $port
+ * @param boolean $secure
+ * @param int $timeout
+ */
+ public function connect($host, $port = 80, $secure = false)
+ {
+
+ }
+
+ /**
+ * Send request to the remote server
+ *
+ * @param string $method
+ * @param Zend_Uri_Http $uri
+ * @param string $http_ver
+ * @param array $headers
+ * @param string $body
+ * @return string Request as string
+ */
+ public function write($method, $uri, $http_ver = "1.1", $headers = array(), $body = "")
+ {
+ $host = $uri->host;
+ $host = (strtolower($uri->scheme) === "https" ? "sslv2://" . $host : $host);
+
+ // Build request headers
+ $path = $uri->path;
+ if ($uri->query) $path .= "?" . $uri->query;
+ $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
+
+ foreach ($headers as $k => $v) {
+ if (is_string($k)) $v = ucfirst($k) . ": $v";
+ $request .= "$v\r\n";
+ }
+
+ // Add the request body
+ $request .= "\r\n" . $body;
+
+ // Do nothing - just return the request as string
+
+ return $request;
+ }
+
+ /**
+ * Return the response set in $this->setResponse()
+ *
+ * @return string
+ */
+ public function read()
+ {
+ if ($this->responseIndex >= count($this->responses)) {
+ $this->responseIndex = 0;
+ }
+
+ return $this->responses[$this->responseIndex++];
+ }
+
+ /**
+ * Close the connection (dummy)
+ *
+ */
+ public function close()
+ {
+
+ }
+
+ /**
+ * Set the HTTP response(s) to be returned by this adapter
+ *
+ * @param Zend_Http_Response|array|string $response
+ */
+ public function setResponse($response)
+ {
+ if ($response instanceof Sabel_Http_Response) {
+ $response = $response->asString("\r\n");
+ }
+
+ $this->responses = (array)$response;
+ $this->responseIndex = 0;
+ }
+
+ /**
+ * Add another response to the response buffer.
+ *
+ * @param string Zend_Http_Response|$response
+ */
+ public function addResponse($response)
+ {
+ if ($response instanceof Sabel_Http_Response) {
+ $response = $response->asString("\r\n");
+ }
+
+ $this->responses[] = $response;
+ }
+
+ /**
+ * Sets the position of the response buffer. Selects which
+ * response will be returned on the next call to read().
+ *
+ * @param integer $index
+ */
+ public function setResponseIndex($index)
+ {
+ if ($index < 0 || $index >= count($this->responses)) {
+ $message = __METHOD__ . "() Index out of range of response buffer size.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $this->responseIndex = $index;
+ }
+}
diff --git a/sabel/i18n/Gettext.php b/sabel/i18n/Gettext.php
new file mode 100755
index 0000000..852b99d
--- /dev/null
+++ b/sabel/i18n/Gettext.php
@@ -0,0 +1,184 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_I18n_Gettext
+{
+ protected static $instance = null;
+
+ protected $fileName = "messages.php";
+ protected $localesDir = "";
+ protected $codeset = array();
+ protected $initialized = false;
+
+ private function __construct()
+ {
+ if (defined("LOCALE_DIR_PATH")) {
+ $this->localesDir = LOCALE_DIR_PATH;
+ } else {
+ $this->localesDir = RUN_BASE . DS . "locale";
+ }
+ }
+
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function isInitialized()
+ {
+ return $this->initialized;
+ }
+
+ public function setMessagesFileName($fileName)
+ {
+ $this->fileName = $fileName;
+
+ if ($this->initialized) {
+ Sabel_I18n_Sabel_Gettext::setMessagesFileName($this->fileName);
+ }
+
+ return $this;
+ }
+
+ public function setLocalesDir($path)
+ {
+ $this->localesDir = $path;
+
+ if ($this->initialized) {
+ Sabel_I18n_Sabel_Gettext::setLocalesDir($path);
+ }
+
+ return $this;
+ }
+
+ public function setCodeset($codeset, $fileName = "")
+ {
+ if ($fileName === "") {
+ $fileName = $this->fileName;
+ }
+
+ $this->codeset[$fileName] = $codeset;
+
+ if ($this->initialized) {
+ Sabel_I18n_Sabel_Gettext::setCodeset($fileName, $codeset);
+ }
+
+ return $this;
+ }
+
+ public function init($acceptLanguage = null)
+ {
+ if ($this->initialized) return;
+
+ if ($languages = $this->getAcceptLanguages($acceptLanguage)) {
+ $dirs = $this->getLocaleDirs();
+ $locale = null;
+ foreach ($languages as $language) {
+ if (strpos($language, "-") !== false) {
+ list ($ll, $cc) = explode("-", $language);
+ $language = $ll . "_" . strtoupper($cc);
+ } else {
+ $ll = "";
+ }
+
+ if (isset($dirs[$language])) {
+ $locale = $language;
+ break;
+ } elseif (isset($dirs[$ll])) {
+ $locale = $ll;
+ break;
+ }
+ }
+
+ if (isset($this->codeset[$this->fileName])) {
+ $codeset = $this->codeset[$this->fileName];
+ } else {
+ $codeset = null;
+ }
+
+ Sabel_I18n_Sabel_Gettext::initialize($this->fileName, $this->localesDir, $codeset, $locale);
+ }
+
+ $this->initialized = true;
+ }
+
+ protected function getAcceptLanguages($acceptLanguage)
+ {
+ $languages = array();
+
+ if ($acceptLanguage === null) {
+ if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) {
+ $acceptLanguage = $_SERVER["HTTP_ACCEPT_LANGUAGE"];
+ } else {
+ return $languages;
+ }
+ }
+
+ if (!empty($acceptLanguage)) {
+ foreach (explode(",", $acceptLanguage) as $lang) {
+ if (strpos($lang, ";") === false) {
+ $q = "1.0";
+ } else {
+ list ($lang, $q) = explode(";", $lang);
+ $q = str_replace("q=", "", $q);
+ }
+
+ $languages[$q] = $lang;
+ }
+
+ krsort($languages, SORT_NUMERIC);
+ $languages = array_values($languages);
+ }
+
+ return $languages;
+ }
+
+ private function getLocaleDirs()
+ {
+ if ((ENVIRONMENT & PRODUCTION) > 0) {
+ $cache = CACHE_DIR_PATH . DS . "sabel" . DS . "locale_dirs.php";
+
+ if (is_file($cache)) {
+ include ($cache);
+ $dirs = $locales;
+ } else {
+ $dirs = $this->_getLocaleDirs();
+ $code = array(" 1,';
+ }
+
+ file_put_contents($cache, implode("", $code) . ");");
+ }
+ } else {
+ $dirs = $this->_getLocaleDirs();
+ }
+
+ return $dirs;
+ }
+
+ private function _getLocaleDirs()
+ {
+ $dir = $this->localesDir . DS;
+ $dirs = array();
+
+ foreach (scandir($dir) as $item) {
+ if ($item === "." || $item === "..") continue;
+ if (is_dir($dir . $item)) $dirs[$item] = 1;
+ }
+
+ return $dirs;
+ }
+}
diff --git a/sabel/i18n/sabel/Gettext.php b/sabel/i18n/sabel/Gettext.php
new file mode 100755
index 0000000..9906501
--- /dev/null
+++ b/sabel/i18n/sabel/Gettext.php
@@ -0,0 +1,108 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_I18n_Sabel_Gettext
+{
+ private static $fileName = "";
+ private static $locale = null;
+ private static $localesDir = array();
+ private static $codeSet = array();
+ private static $messages = array();
+
+ public static function initialize($fileName, $localesDir, $codeSet, $locale)
+ {
+ self::$fileName = $fileName;
+ self::$localesDir = $localesDir;
+ self::$locale = $locale;
+ self::$codeSet[$fileName] = $codeSet;
+ }
+
+ public static function _($msgid)
+ {
+ if (self::$locale === null) {
+ return $msgid;
+ } else {
+ $fileName = self::$fileName;
+ $messages = self::getMessages($fileName);
+
+ if (isset($messages[$msgid])) {
+ $message = ($messages[$msgid] === "") ? $msgid : $messages[$msgid];
+ } else {
+ $message = $msgid;
+ }
+
+ if (isset(self::$codeSet[$fileName])) {
+ $from = self::getInternalEncoding();
+ return mb_convert_encoding($message, self::$codeSet[$fileName], $from);
+ } else {
+ return $message;
+ }
+ }
+ }
+
+ public static function setMessagesFileName($fileName)
+ {
+ self::$fileName = $fileName;
+ }
+
+ public static function setLocalesDir($path)
+ {
+ self::$localesDir = $path;
+ }
+
+ public static function setCodeset($fileName, $codeSet)
+ {
+ self::$codeSet[$fileName] = $codeSet;
+ }
+
+ public static function setLocale($locale)
+ {
+ self::$locale = $locale;
+ }
+
+ private static function getMessages($path)
+ {
+ $locale = self::$locale;
+ $fileName = self::$fileName;
+
+ if (isset(self::$messages[$locale][$fileName])) {
+ return self::$messages[$locale][$fileName];
+ } else {
+ $filePath = self::$localesDir . DS . $locale . DS . $fileName;
+
+ if (is_readable($filePath)) {
+ include ($filePath);
+ return self::$messages[$locale][$fileName] = $messages;
+ } elseif (strpos($locale, "_") !== false) {
+ list ($lang) = explode("_", $locale);
+ $filePath = self::$localesDir . DS . $lang . DS . $fileName;
+
+ if (is_readable($filePath)) {
+ include ($filePath);
+ return self::$messages[$locale][$fileName] = $messages;
+ }
+ }
+ }
+
+ return self::$messages[$locale][$fileName] = false;
+ }
+
+ private static function getInternalEncoding()
+ {
+ static $encoding = null;
+
+ if ($encoding === null) {
+ return $encoding = ini_get("mbstring.internal_encoding");
+ } else {
+ return $encoding;
+ }
+ }
+}
diff --git a/sabel/kvs/Abstract.php b/sabel/kvs/Abstract.php
new file mode 100755
index 0000000..3785f65
--- /dev/null
+++ b/sabel/kvs/Abstract.php
@@ -0,0 +1,24 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Kvs_Abstract implements Sabel_Kvs_Interface
+{
+ protected $prefix = "default";
+
+ public function setKeyPrefix($prefix)
+ {
+ $this->prefix = $prefix;
+ }
+
+ protected function genKey($key)
+ {
+ return $this->prefix . "_" . $key;
+ }
+}
diff --git a/sabel/kvs/Apc.php b/sabel/kvs/Apc.php
new file mode 100755
index 0000000..5d03faa
--- /dev/null
+++ b/sabel/kvs/Apc.php
@@ -0,0 +1,51 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Kvs_Apc extends Sabel_Kvs_Abstract
+{
+ private static $instance = null;
+
+ private function __construct()
+ {
+ if (extension_loaded("apc")) {
+ $this->setKeyPrefix(get_server_name());
+ } else {
+ $message = __METHOD__ . "() apc extension not loaded.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public static function create()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function read($key)
+ {
+ $result = apc_fetch($this->genKey($key));
+ return ($result === false) ? null : $result;
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+ apc_store($this->genKey($key), $value, $timeout);
+ }
+
+ public function delete($key)
+ {
+ $result = $this->read($key);
+ apc_delete($this->genKey($key));
+
+ return $result;
+ }
+}
diff --git a/sabel/kvs/Database.php b/sabel/kvs/Database.php
new file mode 100755
index 0000000..379b944
--- /dev/null
+++ b/sabel/kvs/Database.php
@@ -0,0 +1,132 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Kvs_Database implements Sabel_Kvs_Interface
+{
+ private static $instances = array();
+
+ /**
+ * @var Sabel_Db_Model
+ */
+ protected $model = "";
+
+ private function __construct($mdlName)
+ {
+ $this->model = MODEL($mdlName);
+ }
+
+ public static function create($mdlName = "SblKvs")
+ {
+ if (isset(self::$instances[$mdlName])) {
+ return self::$instances[$mdlName];
+ }
+
+ return self::$instances[$mdlName] = new self($mdlName);
+ }
+
+ public function read($key)
+ {
+ $result = null;
+
+ Sabel_Db_Transaction::activate();
+
+ try {
+ if ($model = $this->fetch($key, true)) {
+ if (($timeout = (int)$model->timeout) === 0) {
+ $result = $model->value;
+ } else {
+ if ($timeout <= time()) {
+ $model->delete();
+ } else {
+ $result = $model->value;
+ }
+ }
+
+ if ($result !== null) {
+ $result = unserialize(str_replace("\\000", "\000", $result));
+ }
+ }
+
+ Sabel_Db_Transaction::commit();
+ } catch (Exception $e) {
+ Sabel_Db_Transaction::rollback();
+ throw $e;
+ }
+
+ return ($result === false) ? null : $result;
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+ Sabel_Db_Transaction::activate();
+
+ try {
+ if ($timeout !== 0) {
+ $timeout = time() + $timeout;
+ }
+
+ $value = str_replace("\000", "\\000", serialize($value));
+
+ if ($model = $this->fetch($key, true)) {
+ $model->save(array(
+ "value" => $value,
+ "timeout" => $timeout
+ ));
+ } else {
+ $this->model->insert(array(
+ "key" => $key,
+ "value" => $value,
+ "timeout" => $timeout,
+ ));
+ }
+
+ Sabel_Db_Transaction::commit();
+ } catch (Exception $e) {
+ Sabel_Db_Transaction::rollback();
+ throw $e;
+ }
+ }
+
+ public function delete($key)
+ {
+ $result = null;
+
+ Sabel_Db_Transaction::activate();
+
+ try {
+ if ($model = $this->fetch($key, true)) {
+ if (($timeout = (int)$model->timeout) !== 0) {
+ if ($timeout > time()) {
+ $result = unserialize(str_replace("\\000", "\000", $model->value));
+ }
+ }
+
+ $model->delete();
+ }
+
+ Sabel_Db_Transaction::commit();
+ } catch (Exception $e) {
+ Sabel_Db_Transaction::rollback();
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ protected function fetch($key, $forUpdate = false)
+ {
+ if ($forUpdate) {
+ $results = $this->model->selectForUpdate($key);
+ return (isset($results[0])) ? $results[0] : null;
+ } else {
+ $model = $this->model->selectOne($key);
+ return ($model->isSelected()) ? $model : null;
+ }
+ }
+}
diff --git a/sabel/kvs/Interface.php b/sabel/kvs/Interface.php
new file mode 100755
index 0000000..74faf6e
--- /dev/null
+++ b/sabel/kvs/Interface.php
@@ -0,0 +1,16 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Kvs_Interface
+{
+ public function read($key);
+ public function write($key, $value, $timeout = 0);
+ public function delete($key);
+}
diff --git a/sabel/kvs/Memcache.php b/sabel/kvs/Memcache.php
new file mode 100755
index 0000000..6d58eb1
--- /dev/null
+++ b/sabel/kvs/Memcache.php
@@ -0,0 +1,63 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Kvs_Memcache extends Sabel_Kvs_Abstract
+{
+ private static $instances = array();
+
+ /**
+ * @var Memcache
+ */
+ protected $memcache = null;
+
+ private function __construct($host, $port)
+ {
+ if (extension_loaded("memcache")) {
+ $this->memcache = new Memcache();
+ $this->addServer($host, $port);
+ $this->setKeyPrefix(get_server_name());
+ } else {
+ $message = __METHOD__ . "() memcache extension not loaded.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public static function create($host = "localhost", $port = 11211)
+ {
+ if (isset(self::$instances[$host][$port])) {
+ return self::$instances[$host][$port];
+ }
+
+ return self::$instances[$host][$port] = new self($host, $port);
+ }
+
+ public function addServer($host, $port = 11211, $weight = 1)
+ {
+ $this->memcache->addServer($host, $port, true, $weight);
+ }
+
+ public function read($key)
+ {
+ $result = $this->memcache->get($this->genKey($key));
+ return ($result === false) ? null : $result;
+ }
+
+ public function write($key, $value, $timeout = 0, $comp = false)
+ {
+ $this->memcache->set($this->genKey($key), $value, $comp, $timeout);
+ }
+
+ public function delete($key)
+ {
+ $result = $this->read($key);
+ $this->memcache->delete($this->genKey($key), 0);
+
+ return $result;
+ }
+}
diff --git a/sabel/kvs/Xml.php b/sabel/kvs/Xml.php
new file mode 100755
index 0000000..0a6b673
--- /dev/null
+++ b/sabel/kvs/Xml.php
@@ -0,0 +1,190 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Kvs_Xml implements Sabel_Kvs_Interface
+{
+ private static $instances = array();
+
+ protected $filePath = "";
+ protected $lockFilePath = "";
+ protected $lockfp = null;
+
+ private function __construct($filePath)
+ {
+ $dir = dirname($filePath);
+
+ if (!is_dir($dir)) {
+ $message = __METHOD__ . "() no such directory '{$dir}'.";
+ throw new Sabel_Exception_DirectoryNotFound($message);
+ }
+
+ $lockFilePath = $filePath . ".lock";
+
+ if (!file_exists($lockFilePath)) {
+ if (touch($lockFilePath)) {
+ chmod($lockFilePath, 0777);
+ } else {
+ $message = __METHOD__ . "() can't create .lock file '{$lockFilePath}'.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ $this->filePath = $filePath;
+ $this->lockFilePath = $lockFilePath;
+
+ $this->lock();
+
+ if (!file_exists($filePath)) {
+ $xml = <<
+
+XML;
+
+ if (file_put_contents($filePath, $xml) === false) {
+ $message = __METHOD__ . "() can't create file '{$filePath}'.";
+ throw new Sabel_Exception_Runtime($message);
+ } else {
+ chmod($filePath, 0777);
+ }
+ }
+
+ $this->unlock();
+ }
+
+ public static function create($filePath)
+ {
+ if (isset(self::$instances[$filePath])) {
+ return self::$instances[$filePath];
+ }
+
+ return self::$instances[$filePath] = new self($filePath);
+ }
+
+ public function read($key)
+ {
+ $result = null;
+
+ $this->lock();
+
+ list ($doc, $element) = $this->getElement($key);
+
+ if ($element) {
+ $result = $this->_read($key, $doc, $element);
+ }
+
+ $this->unlock();
+
+ if ($result === false || $result === "\000") {
+ return null;
+ } else {
+ return $result;
+ }
+ }
+
+ public function write($key, $value, $timeout = 0)
+ {
+ $this->lock();
+
+ list ($doc, $element) = $this->getElement($key);
+
+ if ($timeout !== 0) {
+ $timeout = time() + $timeout;
+ }
+
+ $value = $doc->createCDATASection(
+ str_replace("\000", "\\000", serialize($value))
+ );
+
+ if ($element === null) {
+ $element = $doc->createElement($key);
+ $element->setAttribute("timeout", $timeout);
+ $element->appendChild($value);
+ $doc->documentElement->appendChild($element);
+ } else {
+ $element->setAttribute("timeout", $timeout);
+ $element->replaceChild($value, $element->firstChild);
+ }
+
+ $doc->save($this->filePath);
+
+ $this->unlock();
+ }
+
+ public function delete($key)
+ {
+ $result = null;
+
+ $this->lock();
+
+ list ($doc, $element) = $this->getElement($key);
+
+ if ($element) {
+ $result = $this->_read($key, $doc, $element);
+
+ if ($result === "\000") {
+ $result = null;
+ } else {
+ $element->parentNode->removeChild($element);
+ $doc->save($this->filePath);
+ }
+ }
+
+ $this->unlock();
+
+ return $result;
+ }
+
+ protected function _read($key, $doc, $element)
+ {
+ $result = null;
+
+ if (($timeout = (int)$element->getAttribute("timeout")) === 0) {
+ $result = $element->nodeValue;
+ } else {
+ if ($timeout <= time()) {
+ $element->parentNode->removeChild($element);
+ $doc->save($this->filePath);
+
+ return "\000";
+ } else {
+ $result = $element->nodeValue;
+ }
+ }
+
+ if ($result !== null) {
+ $result = unserialize(str_replace("\\000", "\000", $result));
+ }
+
+ return ($result === false) ? null : $result;
+ }
+
+ protected function getElement($tagName)
+ {
+ $doc = new DOMDocument();
+ $doc->load($this->filePath);
+
+ return array($doc, $doc->documentElement->getElementsByTagName($tagName)->item(0));
+ }
+
+ protected function lock()
+ {
+ $fp = fopen($this->lockFilePath, "r");
+ flock($fp, LOCK_EX);
+
+ $this->lockfp = $fp;
+ }
+
+ protected function unlock()
+ {
+ if ($this->lockfp !== null) {
+ fclose($this->lockfp);
+ $this->lockfp = null;
+ }
+ }
+}
diff --git a/sabel/logger/File.php b/sabel/logger/File.php
new file mode 100755
index 0000000..f71371b
--- /dev/null
+++ b/sabel/logger/File.php
@@ -0,0 +1,94 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Logger_File extends Sabel_Object implements Sabel_Logger_Interface
+{
+ const DEFAULT_LOG_FILE = "sabel.log";
+
+ public function output($allMessages)
+ {
+ if (empty($allMessages)) return;
+
+ foreach ($allMessages as $identifier => $messages) {
+ $fp = $this->open($identifier);
+ $sep = "============================================================" . PHP_EOL;
+ fwrite($fp, PHP_EOL . $sep . PHP_EOL);
+
+ $msgs = array();
+ foreach ($messages as $message) {
+ $msgs[] = $message["time"]
+ . " [" . $this->defineToString($message["level"])
+ . "] " . $message["message"];
+ }
+
+ fwrite($fp, implode(PHP_EOL, $msgs) . PHP_EOL);
+ fclose($fp);
+ }
+ }
+
+ public function write($identifier, $message)
+ {
+ $msg = $message["time"]
+ . " [" . $this->defineToString($message["level"])
+ . "] " . $message["message"];
+
+ $fp = $this->open($identifier);
+ fwrite($fp, $msg . PHP_EOL);
+ fclose($fp);
+ }
+
+ protected function defineToString($level)
+ {
+ switch ($level) {
+ case SBL_LOG_INFO:
+ return "info";
+ case SBL_LOG_DEBUG:
+ return "debug";
+ case SBL_LOG_WARN:
+ return "warning";
+ case SBL_LOG_ERR:
+ return "error";
+ }
+ }
+
+ protected function open($identifier)
+ {
+ $filePath = "";
+
+ if ($identifier === "default") {
+ if (!defined("ENVIRONMENT")) {
+ $name = "test";
+ } else {
+ switch (ENVIRONMENT) {
+ case PRODUCTION:
+ $name = "production";
+ break;
+ case DEVELOPMENT:
+ $name = "development";
+ break;
+ default:
+ $name = "test";
+ }
+ }
+
+ $filePath = LOG_DIR_PATH . DS . $name . "." . self::DEFAULT_LOG_FILE;
+ } else {
+ $filePath = LOG_DIR_PATH . DS . $identifier . ".log";
+ }
+
+ if (!is_file($filePath)) {
+ touch($filePath);
+ chmod($filePath, 0777);
+ }
+
+ return fopen($filePath, "a");
+ }
+}
diff --git a/sabel/logger/Interface.php b/sabel/logger/Interface.php
new file mode 100755
index 0000000..bf8ffa0
--- /dev/null
+++ b/sabel/logger/Interface.php
@@ -0,0 +1,29 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Logger_Interface
+{
+ /**
+ * @param array $messages
+ *
+ * @return void
+ */
+ public function output($messages);
+
+ /**
+ * @param string $identifier
+ * @param array $message
+ *
+ * @return void
+ */
+ public function write($identifier, $message);
+}
diff --git a/sabel/mail/Exception.php b/sabel/mail/Exception.php
new file mode 100755
index 0000000..aa86f38
--- /dev/null
+++ b/sabel/mail/Exception.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Exception extends Sabel_Exception_Runtime
+{
+
+}
diff --git a/sabel/mail/MimeDecode.php b/sabel/mail/MimeDecode.php
new file mode 100755
index 0000000..1ba36af
--- /dev/null
+++ b/sabel/mail/MimeDecode.php
@@ -0,0 +1,546 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_MimeDecode extends Sabel_Object
+{
+ protected $isMbstringLoaded = false;
+
+ public function __construct()
+ {
+ $this->isMbstringLoaded = extension_loaded("mbstring");
+ }
+
+ /**
+ * @param string $source
+ *
+ * @return string
+ */
+ public function decode($source)
+ {
+ $mail = $this->toHeadersAndBody($source);
+
+ if (empty($mail["header"])) {
+ $mail["header"] = $mail["body"];
+ $mail["body"] = "";
+ }
+
+ return $this->_decode($mail["header"], $mail["body"]);
+ }
+
+ protected function _decode($headerText, $body)
+ {
+ $headers = $this->createHeaders($headerText);
+ $content = $this->createContentInfo($headers);
+
+ $mail = new Sabel_Mail_Mime_Decoded();
+
+ $mail->content = $content;
+ $mail->headers = $headers;
+ $mail->body = null;
+ $mail->html = null;
+ $mail->attachments = array();
+ $mail->mails = array(); // multipart/digest
+
+ switch (strtolower($content->getType())) {
+ /*
+ case "multipart/parallel":
+ case "multipart/report":
+ case "multipart/signed":
+ break;
+ */
+
+ case "multipart/mixed":
+ $mixed = $this->_decodeMixedPart($content, $body);
+ $mail->body = $mixed["body"];
+ $mail->html = $mixed["html"];
+ $mail->attachments = $mixed["attachments"];
+ $mail->mails = $mixed["mails"];
+ break;
+
+ case "multipart/alternative":
+ $alter = $this->_decodeAlternativePart($content, $body);
+ $mail->body = $alter["body"];
+ $mail->html = $alter["html"];
+ break;
+
+ case "multipart/related":
+ $related = $this->_decodeRelatedPart($content, $body);
+ $mail->body = $related["body"];
+ $mail->html = $related["html"];
+ break;
+
+ case "multipart/digest":
+ $mail->mails = $this->_decodeDigestPart($content, $body);
+ break;
+
+ default: // simple mail.
+ $part = new stdClass();
+ $part->body = $body;
+ $part->content = $content;
+ $part->type = $content->getType();
+
+ if ($part->type === "text/html") {
+ $mail->html = $this->createMimeObject($part);
+ } else {
+ $mail->body = $this->createMimeObject($part);
+ }
+ break;
+ }
+
+ return $mail;
+ }
+
+ protected function _decodeMixedPart(Sabel_Mail_Mime_Content $content, $body)
+ {
+ if (($boundary = $content->getBoundary()) === "") {
+ $message = __METHOD__ . "() Boundary Not Found.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ $parts = $this->splitByBoundary($body, $boundary);
+ $mixed = array("body" => null, "html" => null, "attachments" => array(), "mails" => array());
+ $notBodyPart = false;
+
+ foreach ($parts as $partOfMessage) {
+ $part = $this->createPart($partOfMessage);
+
+ switch ($part->type) {
+ case "multipart/alternative":
+ $alter = $this->_decodeAlternativePart($part->content, $part->body);
+ $mixed["body"] = $alter["body"];
+ $mixed["html"] = $alter["html"];
+ break;
+
+ case "multipart/related":
+ $related = $this->_decodeRelatedPart($part->content, $part->body);
+ if ($related["body"] !== null) $mixed["body"] = $related["body"];
+ $mixed["html"] = $related["html"];
+ break;
+
+ case "multipart/digest":
+ $mixed["mails"] = $this->_decodeDigestPart($part->content, $part->body);
+ break;
+
+ case "text/html":
+ if (!$notBodyPart) {
+ $mixed["html"] = $this->createMimeObject($part);
+ break;
+ }
+
+ case "text/plain":
+ if (!$notBodyPart) {
+ $mixed["body"] = $this->createMimeObject($part);
+ break;
+ }
+
+ default:
+ $enc = $part->content->getEncoding();
+ $cset = $part->content->getCharset();
+ $data = $this->decodeString($part->body, $enc, $cset, false);
+ $file = new Sabel_Mail_Mime_File($part->content->getName(), $data, $part->type);
+ $file->setCharset($cset);
+ $file->setEncoding($enc);
+ $file->setDisposition($part->content->getDisposition());
+ $file->setHeaders($part->headers);
+ $mixed["attachments"][] = $file;
+ }
+
+ $notBodyPart = true;
+ }
+
+ return $mixed;
+ }
+
+ protected function _decodeAlternativePart(Sabel_Mail_Mime_Content $content, $body)
+ {
+ if (($boundary = $content->getBoundary()) === "") {
+ $message = __METHOD__ . "() Boundary Not Found.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ $parts = $this->splitByBoundary($body, $boundary);
+ $alter = array("body" => null, "html" => null);
+
+ foreach ($parts as $partOfMessage) {
+ $part = $this->createPart($partOfMessage);
+
+ switch ($part->type) {
+ case "text/plain":
+ $alter["body"] = $this->createMimeObject($part);
+ break;
+
+ case "text/html":
+ $alter["html"] = $this->createMimeObject($part);
+ break;
+
+ case "multipart/related":
+ $related = $this->_decodeRelatedPart($part->content, $part->body);
+ if ($related["body"] !== null) $alter["body"] = $related["body"];
+ $alter["html"] = $related["html"];
+ break;
+
+ default:
+ $message = __METHOD__ . "() {$part->type} is not supported in multipart/alternative.";
+ throw new Sabel_Mail_Mime_Exception($message);
+ }
+ }
+
+ return $alter;
+ }
+
+ protected function _decodeRelatedPart(Sabel_Mail_Mime_Content $content, $body)
+ {
+ if (($boundary = $content->getBoundary()) === "") {
+ $message = __METHOD__ . "() Boundary Not Found.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ $parts = $this->splitByBoundary($body, $boundary);
+ $related = array("body" => null, "html" => null);
+
+ foreach ($parts as $partOfMessage) {
+ $part = $this->createPart($partOfMessage);
+
+ if ($part->type === "text/html") {
+ $related["html"] = $this->createMimeObject($part);
+ } elseif ($part->type === "multipart/alternative") {
+ $alter = $this->_decodeAlternativePart($part->content, $part->body);
+ $related["body"] = $alter["body"];
+ $related["html"] = $alter["html"];
+ } else { // inline images.
+ $enc = $part->content->getEncoding();
+ $body = $this->decodeString($part->body, $enc, $part->content->getCharset(), false);
+ $cid = (isset($part->headers["content-id"])) ? $part->headers["content-id"] : "";
+ $related["html"]->addImage($cid, array(
+ "data" => $body, "mimetype" => $part->type, "encoding" => $enc
+ ));
+ }
+ }
+
+ return $related;
+ }
+
+ protected function _decodeDigestPart(Sabel_Mail_Mime_Content $content, $body)
+ {
+ if (($boundary = $content->getBoundary()) === "") {
+ $message = __METHOD__ . "() Boundary Not Found.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ $parts = $this->splitByBoundary($body, $boundary);
+ $mails = array();
+
+ foreach ($parts as $partOfMessage) {
+ $part = $this->createPart($partOfMessage);
+ if ($part->type === "message/rfc822") {
+ $_part = $this->toHeadersAndBody($part->body);
+ $mails[] = $this->_decode($_part["header"], $_part["body"]);
+ } else {
+ $message = __METHOD__ . "() {$part->type} is not supported in multipart/digest.";
+ throw new Sabel_Mail_Mime_Exception($message);
+ }
+ }
+
+ return $mails;
+ }
+
+ protected function createMimeObject(stdClass $part)
+ {
+ $body = $part->body;
+ $content = $part->content;
+ $ctype = $part->type;
+ $headers = (isset($part->headers)) ? $part->headers : array();
+
+ if ($ctype === "text/plain" || $ctype === "text/html") {
+ $cset = $content->getCharset();
+ $enc = $content->getEncoding();
+ $body = $this->decodeString($body, $enc, $cset, false);
+ $mime = ($ctype === "text/plain") ? new Sabel_Mail_Mime_Plain($body) : new Sabel_Mail_Mime_Html($body);
+ $mime->setHeaders($headers);
+ $mime->setCharset($cset);
+ $mime->setEncoding($enc);
+ $mime->setDisposition($content->getDisposition());
+
+ return $mime;
+ } else {
+ $message = __METHOD__ . "() {$ctype} is not supported now.";
+ throw new Sabel_Mail_Mime_Exception($message);
+ }
+ }
+
+ protected function createPart($partOfMessage)
+ {
+ $part = new stdClass();
+ $_tmp = $this->toHeadersAndBody($partOfMessage);
+
+ $part->header = $_tmp["header"];
+ $part->body = $_tmp["body"];
+ $part->headers = $this->createHeaders($_tmp["header"]);
+ $part->content = $this->createContentInfo($part->headers);
+ $part->type = $part->content->getType();
+
+ return $part;
+ }
+
+ protected function createContentInfo($headers)
+ {
+ $content = new Sabel_Mail_Mime_Content();
+
+ foreach ($headers as $key => $value) {
+ switch ($key) {
+ case "content-type":
+ $values = $this->parseHeaderValue($value);
+ $content->setType($values["value"]);
+ if (isset($values["boundary"])) $content->setBoundary($values["boundary"]);
+ if (isset($values["charset"])) $content->setCharset(strtoupper($values["charset"]));
+ break;
+
+ case "content-disposition":
+ $values = $this->parseHeaderValue($value);
+ $content->setDisposition($values["value"]);
+
+ $filename = null;
+ if (isset($values["filename"])) {
+ $filename = $values["filename"];
+ } elseif (isset($values["filename*"]) || isset($values["filename*0*"]) || isset($values["filename*0"])) {
+ $buffer = array();
+ foreach ($values as $k => $v) {
+ if (strpos($k, "filename*") !== false) {
+ $buffer[] = $v;
+ }
+ }
+
+ $filename = implode("", $buffer);
+ }
+
+ if ($filename !== null) {
+ $content->setName($this->decodeFileName($filename));
+ }
+ break;
+
+ case "content-transfer-encoding":
+ $values = $this->parseHeaderValue($value);
+ $content->setEncoding($values["value"]);
+ break;
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * @param string $source
+ *
+ * @return array
+ */
+ public function toHeadersAndBody($source)
+ {
+ if (preg_match("/^(.+?)(\r\n\r\n|\n\n|\r\r)(.+)/s", $source, $matches) === 1) {
+ $chars = (strlen($matches[2]) === 4) ? substr($matches[2], 0, 2) : $matches[2]{0};
+ return array("header" => $matches[1], "body" => rtrim($matches[3], $chars));
+ } else {
+ return array("header" => "", "body" => $source);
+ }
+ }
+
+ /**
+ * @param string $headerText
+ *
+ * @return array
+ */
+ public function createHeaders($headerText)
+ {
+ $headers = array();
+ if ($headerText === "") return $headers;
+
+ preg_match("/(\r\n|\n|\r)/", $headerText, $matches);
+
+ if (!isset($matches[0])) {
+ $_tmp = array($headerText);
+ } else {
+ $eol = $matches[0];
+ $headerText = preg_replace("/{$eol}(\t|\s)+/", " ", $headerText);
+ $_tmp = explode($eol, $headerText);
+ }
+
+ foreach ($_tmp as $i => $line) {
+ unset($_tmp[$i]);
+ if ($line === "") break;
+
+ @list ($key, $value) = explode(":", $line, 2);
+ $value = $this->decodeHeader(ltrim($value));
+
+ if (isset($headers[$key])) {
+ if (is_array($headers[$key])) {
+ $headers[$key][] = $value;
+ } else {
+ $headers[$key] = array($headers[$key], $value);
+ }
+ } else {
+ $headers[$key] = $value;
+ }
+ }
+
+ return array_change_key_case($headers);
+ }
+
+ /**
+ * @param string $str
+ *
+ * @return array
+ */
+ public function parseHeaderValue($str)
+ {
+ $values = array();
+ $values["params"] = array();
+
+ if (($pos = strpos($str, ";")) === false) {
+ $values["value"] = $str;
+ return $values;
+ }
+
+ $regex = '/".+[^\\\\]"|\'.+[^\\\\]\'/U';
+ $str = preg_replace_callback($regex, create_function('$matches', '
+ return str_replace(";", "__%SC%__", $matches[0]);
+ '), $str);
+
+ $values["value"] = substr($str, 0, $pos);
+ $str = ltrim(substr($str, $pos + 1));
+
+ if ($str === "" || $str === ";") {
+ return $values;
+ }
+
+ foreach (array_map("trim", explode(";", $str)) as $param) {
+ if ($param === "") continue;
+ @list ($key, $value) = explode("=", $param, 2);
+ $key = strtolower($key);
+
+ if ($value === null) {
+ $values["params"][] = $key;
+ } else {
+ $quote = $value{0};
+ if ($quote === '"' || $quote === "'") {
+ if ($quote === substr($value, -1, 1)) {
+ $value = str_replace("\\{$quote}", $quote, substr($value, 1, -1));
+ }
+ }
+
+ $values[$key] = str_replace("__%SC%__", ";", $value);
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * @param string $body
+ * @param string $boundary
+ *
+ * @return array
+ */
+ public function splitByBoundary($body, $boundary)
+ {
+ $parts = array_map("ltrim", explode("--" . $boundary, $body));
+
+ array_shift($parts);
+ array_pop($parts);
+
+ return $parts;
+ }
+
+ /**
+ * @param string $str
+ *
+ * @return string
+ */
+ public function decodeHeader($str)
+ {
+ $regex = "/=\?([^?]+)\?(q|b)\?([^?]*)\?=/i";
+ $count = preg_match_all($regex, $str, $matches);
+ if ($count < 1) return $str;
+
+ $str = str_replace("?= =?", "?==?", $str);
+
+ for ($i = 0; $i < $count; $i++) {
+ $encoding = (strtolower($matches[2][$i]) === "b") ? "base64" : "quoted-printable";
+ $value = $this->decodeString($matches[3][$i], $encoding, $matches[1][$i]);
+ $str = str_replace($matches[0][$i], $value, $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * @param string $filename
+ *
+ * @return string
+ */
+ public function decodeFileName($filename)
+ {
+ if (preg_match("/^([a-zA-Z0-9\-]+)'([a-z]{2-5})?'(%.+)$/", $filename, $matches) === 1) { // RFC2231
+ return $this->decodeString(urldecode($matches[3]), "", $matches[1]);
+ } elseif (preg_match("/=\?([^?]+)\?(q|b)\?([^?]*)\?=/i", $filename, $matches) === 1) {
+ $encoding = (strtolower($matches[2]) === "b") ? "base64" : "quoted-printable";
+ return $this->decodeString($matches[3], $encoding, $matches[1]);
+ } else {
+ return $filename;
+ }
+ }
+
+ /**
+ * @param string $str
+ * @param string $encoding
+ * @param string $charset
+ *
+ * @return string
+ */
+ public function decodeString($str, $encoding, $charset, $isHeader = true)
+ {
+ switch (strtolower($encoding)) {
+ case "base64":
+ $str = base64_decode($str);
+ break;
+
+ case "quoted-printable":
+ $str = Sabel_Mail_QuotedPrintable::decode($str, $isHeader);
+ break;
+ }
+
+ if ($this->isMbstringLoaded && $charset) {
+ return $this->mbConvertEncoding($str, $charset);
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * @param string $str
+ * @param string $fromEnc
+ *
+ * @return string
+ */
+ protected function mbConvertEncoding($str, $fromEnc)
+ {
+ static $internalEncoding = null;
+
+ if ($internalEncoding === null) {
+ $internalEncoding = strtoupper(mb_internal_encoding());
+ }
+
+ $fromEnc = strtoupper($fromEnc);
+ if ($internalEncoding === $fromEnc) {
+ return $str;
+ } else {
+ return mb_convert_encoding($str, $internalEncoding, $fromEnc);
+ }
+ }
+}
diff --git a/sabel/mail/QuotedPrintable.php b/sabel/mail/QuotedPrintable.php
new file mode 100755
index 0000000..169c695
--- /dev/null
+++ b/sabel/mail/QuotedPrintable.php
@@ -0,0 +1,44 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_QuotedPrintable
+{
+ public static function encode($str, $lineLength = 74, $lineEnd = "\r\n")
+ {
+ $fp = fopen("php://temp", "r+");
+
+ stream_filter_append(
+ $fp,
+ "convert.quoted-printable-encode",
+ STREAM_FILTER_READ,
+ array(
+ "line-length" => $lineLength,
+ "line-break-chars" => $lineEnd
+ )
+ );
+
+ fputs($fp, $str);
+ rewind($fp);
+ $encoded = str_replace("_", "=5F", stream_get_contents($fp));
+ fclose($fp);
+
+ return $encoded;
+ }
+
+ public static function decode($str, $isHeader = true)
+ {
+ if ($isHeader) {
+ $str = str_replace("_", " ", $str);
+ }
+
+ return quoted_printable_decode($str);
+ }
+}
diff --git a/sabel/mail/mime/Abstract.php b/sabel/mail/mime/Abstract.php
new file mode 100755
index 0000000..3666a8c
--- /dev/null
+++ b/sabel/mail/mime/Abstract.php
@@ -0,0 +1,168 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Mail_Mime_Abstract
+{
+ /**
+ * @var array
+ */
+ protected $headers = array();
+
+ /**
+ * @var string
+ */
+ protected $charset = "ISO-8859-1";
+
+ /**
+ * @var string
+ */
+ protected $content = "";
+
+ /**
+ * @var string
+ */
+ protected $encoding = "7bit";
+
+ /**
+ * @var string
+ */
+ protected $disposition = "inline";
+
+ /**
+ * @param array $headers
+ *
+ * @return void
+ */
+ public function setHeaders(array $headers)
+ {
+ $this->headers = $headers;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return string
+ */
+ public function getHeader($name)
+ {
+ $lowered = strtolower($name);
+ if (isset($this->headers[$lowered])) {
+ return $this->headers[$lowered];
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * @return const Sabel_Mail_Mime_Abstract
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param string $content
+ *
+ * @return void
+ */
+ public function setContent($content)
+ {
+ $this->content = $content;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * @param string $charset
+ *
+ * @return void
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * @param string $encoding
+ *
+ * @return void
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = strtolower($encoding);
+ }
+
+ /**
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * @param string $disposition
+ *
+ * @return void
+ */
+ public function setDisposition($disposition)
+ {
+ $this->disposition = strtolower($disposition);
+ }
+
+ /**
+ * @return string
+ */
+ public function getDisposition()
+ {
+ return $this->disposition;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEncodedContent()
+ {
+ $content = $this->content;
+ if (extension_loaded("mbstring")) {
+ $content = mb_convert_encoding($content, $this->charset);
+ }
+
+ return $this->encode($content, $this->encoding, Sabel_Mail::getEol());
+ }
+
+ protected function encode($str, $encoding, $eol = "\r\n", $length = Sabel_Mail::LINELENGTH)
+ {
+ switch (strtolower($encoding)) {
+ case "base64":
+ return rtrim(chunk_split(base64_encode($str), $length, $eol));
+ case "quoted-printable":
+ return Sabel_Mail_QuotedPrintable::encode($str, $length, $eol);
+ default:
+ return $str;
+ }
+ }
+}
diff --git a/sabel/mail/mime/Content.php b/sabel/mail/mime/Content.php
new file mode 100755
index 0000000..4a4dc52
--- /dev/null
+++ b/sabel/mail/mime/Content.php
@@ -0,0 +1,80 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Mime_Content extends Sabel_Object
+{
+ protected $type = "";
+ protected $charset = "";
+ protected $name = "";
+ protected $boundary = "";
+ protected $encoding = "";
+ protected $disposition = "";
+
+ public function setType($type)
+ {
+ $this->type = $type;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setBoundary($boundary)
+ {
+ $this->boundary = $boundary;
+ }
+
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function setEncoding($encoding)
+ {
+ $this->encoding = strtolower($encoding);
+ }
+
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ public function setDisposition($disposition)
+ {
+ $this->disposition = strtolower($disposition);
+ }
+
+ public function getDisposition()
+ {
+ return $this->disposition;
+ }
+}
diff --git a/sabel/mail/mime/Decoded.php b/sabel/mail/mime/Decoded.php
new file mode 100755
index 0000000..d29da7d
--- /dev/null
+++ b/sabel/mail/mime/Decoded.php
@@ -0,0 +1,111 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Mime_Decoded extends Sabel_Object
+{
+ public $content = null;
+ public $headers = array();
+ public $body = null;
+ public $html = null;
+ public $attachments = array();
+ public $mails = array(); // multipart/digest
+
+ public function getHeader($name)
+ {
+ $lowered = strtolower($name);
+
+ if (isset($this->headers[$lowered])) {
+ return $this->headers[$lowered];
+ } else {
+ return "";
+ }
+ }
+
+ public function getFromAddr()
+ {
+ $from = $this->getHeader("From");
+ if ($from === "") return "";
+
+ preg_match('/<([^@]+@[^@]+)>/', $from, $matches);
+ return (isset($matches[1])) ? $matches[1] : $from;
+ }
+
+ public function getFromName()
+ {
+ $from = $this->getHeader("From");
+ if ($from === "") return "";
+
+ return $this->removeQuote(preg_replace('/<[^@]+@[^@]+>/', "", $from));
+ }
+
+ public function getToAddr()
+ {
+ $to = $this->getHeader("To");
+ if ($to === "") return "";
+
+ if (strpos($to, ",") === false) {
+ preg_match('/<([^@]+@[^@]+)>/', $to, $matches);
+ return (isset($matches[1])) ? $matches[1] : $to;
+ } else {
+ $ret = array();
+ foreach (array_map("trim", explode(",", $to)) as $t) {
+ preg_match('/<([^@]+@[^@]+)>/', $t, $matches);
+ $ret[] = (isset($matches[1])) ? $matches[1] : $t;
+ }
+
+ return $ret;
+ }
+ }
+
+ public function getToName()
+ {
+ $to = $this->getHeader("To");
+ if ($to === "") return "";
+
+ if (strpos($to, ",") === false) {
+ return $this->removeQuote(preg_replace('/<[^@]+@[^@]+>/', "", $to));
+ } else {
+ $ret = array();
+ foreach (array_map("trim", explode(",", $to)) as $t) {
+ $ret[] = $this->removeQuote(preg_replace('/<[^@]+@[^@]+>/', "", $t));
+ }
+
+ return $ret;
+ }
+ }
+
+ public function getSubject()
+ {
+ return $this->getHeader("Subject");
+ }
+
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ public function getHtml()
+ {
+ return $this->html;
+ }
+
+ protected function removeQuote($str)
+ {
+ if ($str{0} === '"') {
+ $_tmp = trim($str);
+ if ($_tmp{strlen($_tmp) - 1} === '"') {
+ $str = substr($_tmp, 1, -1);
+ }
+ }
+
+ return $str;
+ }
+}
diff --git a/sabel/mail/mime/Exception.php b/sabel/mail/mime/Exception.php
new file mode 100755
index 0000000..e12d5e2
--- /dev/null
+++ b/sabel/mail/mime/Exception.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Mime_Exception extends Sabel_Exception_Runtime
+{
+
+}
diff --git a/sabel/mail/mime/File.php b/sabel/mail/mime/File.php
new file mode 100755
index 0000000..5232c38
--- /dev/null
+++ b/sabel/mail/mime/File.php
@@ -0,0 +1,126 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Mime_File extends Sabel_Mail_Mime_Abstract
+{
+ /**
+ * @var string
+ */
+ protected $name = "";
+
+ /**
+ * @var string
+ */
+ protected $type = "";
+
+ /**
+ * @var boolean
+ */
+ protected $followRFC2231 = false;
+
+ public function __construct($name, $content, $type, $followRFC2231 = false)
+ {
+ $this->name = $name;
+ $this->type = $type;
+
+ $this->content = $content;
+ $this->followRFC2231 = $followRFC2231;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return void
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function toMailPart()
+ {
+ $eol = Sabel_Mail::getEol();
+ $encoding = $this->encoding;
+ $disposition = $this->disposition;
+ $data = $this->encode($this->content, $encoding, $eol);
+
+ $part = array();
+
+ if ($this->followRFC2231) {
+ $part[] = "Content-Type: " . $this->type;
+ $part[] = "Content-Disposition: " . $this->disposition . ";";
+ $part[] = $this->toRFC2231($this->name, $eol);
+ $part[] = "Content-Transfer-Encoding: {$encoding}";
+ $part[] = $eol . $data . $eol;
+ } else {
+ if (extension_loaded("mbstring")) {
+ $name = mb_encode_mimeheader($this->name, $this->charset);
+ } else {
+ $name = "=?{$this->charset}?B?" . base64_encode($this->name) . "?=";
+ }
+
+ $part[] = "Content-Type: " . $this->type . "; name=\"{$name}\"";
+ $part[] = "Content-Disposition: " . $this->disposition . "; filename=\"{$name}\"";
+ $part[] = "Content-Transfer-Encoding: {$encoding}";
+ $part[] = $eol . $data . $eol;
+ }
+
+ return implode($eol, $part);
+ }
+
+ protected function toRFC2231($name, $eol)
+ {
+ if (extension_loaded("mbstring")) {
+ $name = mb_convert_encoding($name, $this->charset);
+ }
+
+ if (preg_match('/^[a-zA-Z0-9_\.]+$/', $name) === 1) {
+ return " filename*0={$this->charset}''{$name}";
+ }
+
+ $exploded = explode("%", urlencode($name));
+ array_shift($exploded);
+ $blocks = array();
+ for ($i = 0; $i < 1000; $i++) {
+ $res = array_slice($exploded, $i * 13, 13);
+ if (empty($res)) break;
+ $blocks[] = $res;
+ }
+
+ if (count($blocks) === 1) {
+ return " filename*0*={$this->charset}''%" . implode("%", $blocks[0]);
+ } else {
+ $last = array_pop($blocks);
+ $names = array();
+ foreach ($blocks as $i => $block) {
+ if ($i === 0) {
+ $names[] = " filename*{$i}*={$this->charset}''%" . implode("%", $block) . ";";
+ } else {
+ $names[] = " filename*{$i}*=%" . implode("%", $block) . ";";
+ }
+ }
+
+ $names[] = " filename*" . ++$i . "*=%" . implode("%", $last);
+ return implode($eol, $names);
+ }
+ }
+}
diff --git a/sabel/mail/mime/Html.php b/sabel/mail/mime/Html.php
new file mode 100755
index 0000000..4c70c7f
--- /dev/null
+++ b/sabel/mail/mime/Html.php
@@ -0,0 +1,124 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Mime_Html extends Sabel_Mail_Mime_Abstract
+{
+ /**
+ * @var array
+ */
+ protected $inlineImages = array();
+
+ /**
+ * @var string
+ */
+ protected $type = "text/html";
+
+ public function __construct($content)
+ {
+ $this->content = $content;
+ }
+
+ /**
+ * @param string $contentId unique id
+ * @param array $image 'data' or 'path' are required.
+ * 'data' => image data,
+ * 'path' => image path,
+ * 'mimetype' => mime type,
+ * 'encoding' => encoding('base64'(default) or 'quoted-printable')
+ *
+ * @return self
+ */
+ public function addImage($contentId, $image)
+ {
+ if (is_array($image)) {
+ foreach ($image as $k => $v) {
+ if (($lk = strtolower($k)) !== $k) {
+ $image[$lk] = $v;
+ unset($image[$k]);
+ }
+ }
+ } else {
+ $image = array("path" => $image);
+ }
+
+ if (!isset($image["data"]) && !isset($image["path"])) {
+ $message = __METHOD__ . "() must set arg2[data](image data) or arg2[path](image path).";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ if (!isset($image["data"])) {
+ if (($image["data"] = @file_get_contents($image["path"])) === false) {
+ $message = __METHOD__ . "() failed to open stream: No such file or directory in '{$image['path']}'.";
+ throw new Sabel_Exception_FileNotFound($message);
+ }
+ }
+
+ if (!isset($image["encoding"])) {
+ $image["encoding"] = "base64";
+ }
+
+ if (!isset($image["mimetype"])) {
+ $mimetype = get_mime_type($image["data"]);
+ $image["mimetype"] = (!$mimetype) ? "application/octet-stream" : $mimetype;
+ }
+
+ $this->inlineImages[] = array(
+ "cid" => $contentId,
+ "data" => $image["data"],
+ "mimetype" => $image["mimetype"],
+ "encoding" => $image["encoding"]
+ );
+
+ return $this;
+ }
+
+ public function getImages()
+ {
+ return $this->inlineImages;
+ }
+
+ public function hasImage()
+ {
+ return !empty($this->inlineImages);
+ }
+
+ /**
+ * @return string
+ */
+ public function toMailPart($boundary = null)
+ {
+ if ($this->hasImage() && $boundary === null) {
+ $message = __METHOD__ . "() Because the inline image exists, boundary is necessary.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ $part = array();
+ $eol = Sabel_Mail::getEol();
+
+ $part[] = "Content-Disposition: " . $this->disposition;
+ $part[] = "Content-Transfer-Encoding: " . $this->encoding;
+ $part[] = "Content-Type: {$this->type}; charset=" . $this->charset . $eol;
+ $part[] = $this->getEncodedContent() . $eol;
+
+ if ($this->hasImage()) {
+ foreach ($this->inlineImages as $image) {
+ $enc = $image["encoding"];
+ $part[] = "--{$boundary}";
+ $part[] = "Content-Type: {$image["mimetype"]}";
+ $part[] = "Content-Transfer-Encoding: $enc";
+ $part[] = "Content-ID: <{$image["cid"]}>";
+ $part[] = $eol . $this->encode($image["data"], $enc, $eol) . $eol;
+ }
+ }
+
+ return implode($eol, $part);
+ }
+}
diff --git a/sabel/mail/mime/Plain.php b/sabel/mail/mime/Plain.php
new file mode 100755
index 0000000..a4377bf
--- /dev/null
+++ b/sabel/mail/mime/Plain.php
@@ -0,0 +1,39 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Mime_Plain extends Sabel_Mail_Mime_Abstract
+{
+ /**
+ * @var string
+ */
+ protected $type = "text/plain";
+
+ public function __construct($content)
+ {
+ $this->content = $content;
+ }
+
+ /**
+ * @return string
+ */
+ public function toMailPart()
+ {
+ $part = array();
+ $eol = Sabel_Mail::getEol();
+
+ $part[] = "Content-Disposition: " . $this->disposition;
+ $part[] = "Content-Transfer-Encoding: " . $this->encoding;
+ $part[] = "Content-Type: {$this->type}; charset=" . $this->charset . $eol;
+ $part[] = $this->getEncodedContent() . $eol;
+
+ return implode($eol, $part);
+ }
+}
diff --git a/sabel/mail/sender/Interface.php b/sabel/mail/sender/Interface.php
new file mode 100755
index 0000000..b544be2
--- /dev/null
+++ b/sabel/mail/sender/Interface.php
@@ -0,0 +1,16 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Mail_Sender_Interface
+{
+ public function send(array $headers, $body, $options = array());
+}
diff --git a/sabel/mail/sender/PHP.php b/sabel/mail/sender/PHP.php
new file mode 100755
index 0000000..03ace23
--- /dev/null
+++ b/sabel/mail/sender/PHP.php
@@ -0,0 +1,89 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Sender_PHP
+ extends Sabel_Object implements Sabel_Mail_Sender_Interface
+{
+ public function send(array $headers, $body, $options = array())
+ {
+ $recipients = $this->getRecipients($headers);
+ $subject = $this->getSubject($headers);
+ $headersText = implode(Sabel_Mail::getEol(), $this->createHeaderText($headers));
+
+ if (isset($options["parameters"])) {
+ return mail($recipients, $subject, $body, $headersText, $options["parameters"]);
+ } else {
+ return mail($recipients, $subject, $body, $headersText);
+ }
+ }
+
+ protected function createHeaderText($headersArray)
+ {
+ $headers = array();
+
+ foreach ($headersArray as $name => $header) {
+ if ($name === "From") {
+ if ($header["name"] === "") {
+ $headers[] = "From: <{$header["address"]}>";
+ } else {
+ $headers[] = "From: {$header["name"]} <{$header["address"]}>";
+ }
+ } elseif ($name === "Cc") {
+ foreach ($header as $value) {
+ if ($value["name"] === "") {
+ $headers[] = "Cc: <{$value["address"]}>";
+ } else {
+ $headers[] = "Cc: {$value["name"]} <{$value["address"]}>";
+ }
+ }
+ } elseif (is_array($header)) {
+ foreach ($header as $value) {
+ $headers[] = $name . ": " . $value;
+ }
+ } else {
+ $headers[] = $name . ": " . $header;
+ }
+ }
+
+ return $headers;
+ }
+
+ protected function getRecipients(&$headers)
+ {
+ $recipients = array();
+ if (isset($headers["To"])) {
+ foreach ($headers["To"] as $recipient) {
+ if ($recipient["name"] === "") {
+ $recipients[] = $recipient["address"];
+ } else {
+ $recipients[] = $recipient["name"] . " <{$recipient["address"]}>";
+ }
+ }
+
+ unset($headers["To"]);
+ return implode(", ", $recipients);
+ } else {
+ $message = __METHOD__ . "() empty recipients.";
+ throw new Sabel_Mail_Exception($message);
+ }
+ }
+
+ protected function getSubject(&$headers)
+ {
+ $subject = "";
+ if (isset($headers["Subject"])) {
+ $subject = $headers["Subject"];
+ unset($headers["Subject"]);
+ }
+
+ return $subject;
+ }
+}
diff --git a/sabel/mail/sender/Smtp.php b/sabel/mail/sender/Smtp.php
new file mode 100755
index 0000000..f863ec5
--- /dev/null
+++ b/sabel/mail/sender/Smtp.php
@@ -0,0 +1,235 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Sender_Smtp
+ extends Sabel_Object implements Sabel_Mail_Sender_Interface
+{
+ const DEFAULT_TIMEOUT = 5;
+
+ /**
+ * @var array
+ */
+ protected $config = array();
+
+ /**
+ * @var resource
+ */
+ protected $smtp = null;
+
+ /**
+ * @var string
+ */
+ protected $eol = "\r\n";
+
+ public function __construct(array $config = array())
+ {
+ if (!isset($config["host"])) {
+ $config["host"] = "localhost";
+ }
+
+ if (isset($config["eol"])) {
+ $this->eol = $config["eol"];
+ }
+
+ if (!isset($config["timeout"])) {
+ $config["timeout"] = self::DEFAULT_TIMEOUT;
+ }
+
+ $this->config = $config;
+ $this->connect();
+ }
+
+ public function disconnect()
+ {
+ if (is_resource($this->smtp)) {
+ fclose($this->smtp);
+ }
+ }
+
+ public function send(array $headers, $body, $options = array())
+ {
+ $this->command("MAIL FROM:<{$headers["From"]["address"]}>", "250");
+ $this->sendRcptTo($headers);
+ $this->command("DATA", "354");
+ $this->sendHeaders($headers);
+ $this->command($this->eol . $body . $this->eol);
+ $this->command(".", "250");
+ }
+
+ protected function connect()
+ {
+ $server = $this->config["host"];
+
+ if (isset($this->config["port"])) {
+ $port = $this->config["port"];
+ } else {
+ $_tmp = strtolower(substr($server, 0, 6));
+ $port = ($_tmp === "ssl://" || $_tmp === "tls://") ? "465" : "25";
+ }
+
+ $smtp = fsockopen($server, $port, $errno, $errstr, $this->config["timeout"]);
+
+ if ($smtp === false || strpos(rtrim(fgets($smtp)), "220") !== 0) {
+ $message = __METHOD__ . "()" ." can't connect to the SMTP Server. "
+ . "HOST => '{$server}, PORT => '{$port}' / reason: {$errno}/{$errstr}";
+
+ throw new Sabel_Mail_Smtp_Exception_ConnectionRefused($message);
+ }
+
+ $this->smtp = $smtp;
+ $this->command("EHLO {$server}");
+
+ while ($result = trim(fgets($this->smtp))) {
+ if (strpos($result, "250 ") === 0) break;
+
+ if (strpos($result, "250") !== 0) {
+ preg_match("/^[0-9]+/", $result, $matches);
+ $message = "got unexpected response code '{$matches[0]}'.";
+ $exception = new Sabel_Mail_Smtp_Exception($message);
+ $exception->setResponseCode($matches[0]);
+ throw $exception;
+ }
+ }
+
+ if (isset($this->config["auth"])) {
+ try {
+ $this->_auth($this->config["auth"]);
+ } catch (Sabel_Mail_Smtp_Exception $e) {
+ $exception = new Sabel_Mail_Smtp_Exception_AuthFailure($e->getMessage());
+ $exception->setResponseCode($e->getResponseCode());
+ throw $exception;
+ }
+ }
+ }
+
+ protected function sendRcptTo($headers)
+ {
+ if (empty($headers["To"])) {
+ $message = __METHOD__ . "() empty recipients.";
+ throw new Sabel_Mail_Exception($message);
+ }
+
+ try {
+ foreach ($headers["To"] as $rcpt) {
+ $this->command("RCPT TO:<{$rcpt["address"]}>", "250");
+ }
+
+ if (isset($headers["Cc"])) {
+ foreach ($headers["Cc"] as $rcpt) {
+ $this->command("RCPT TO:<{$rcpt["address"]}>", "250");
+ }
+ }
+
+ if (isset($headers["Bcc"])) {
+ foreach ($headers["Bcc"] as $rcpt) {
+ $this->command("RCPT TO:<{$rcpt}>", "250");
+ }
+ }
+ } catch (Sabel_Mail_Smtp_Exception $e) {
+ $exception = new Sabel_Mail_Smtp_Exception_RecipientRefused($e->getMessage());
+ $exception->setResponseCode($e->getResponseCode());
+ throw $exception;
+ }
+ }
+
+ protected function sendHeaders($headers)
+ {
+ foreach ($headers as $name => $header) {
+ if ($name === "From") {
+ if ($header["name"] === "") {
+ $this->command("From: {$header["address"]}");
+ } else {
+ $this->command("From: {$header["name"]} <{$header["address"]}>");
+ }
+ } elseif ($name === "To" || $name === "Cc") {
+ $value = array();
+ foreach ($header as $rcpt) {
+ if ($rcpt["name"] === "") {
+ $value[] = $rcpt["address"];
+ } else {
+ $value[] = "{$rcpt["name"]} <{$rcpt["address"]}>";
+ }
+ }
+
+ $this->command($name . ": " . implode(", ", $value));
+ } elseif (is_array($header)) {
+ foreach ($header as $value) {
+ $this->command("{$name}: {$value}");
+ }
+ } else {
+ $this->command("{$name}: {$header}");
+ }
+ }
+ }
+
+ protected function plainAuth($user, $password)
+ {
+ $command = "AUTH PLAIN " . base64_encode("{$user}\000{$user}\000{$password}");
+ $this->command($command, "235");
+ }
+
+ protected function loginAuth($user, $password)
+ {
+ $this->command("AUTH LOGIN", "334");
+ $this->command(base64_encode($user), "334");
+ $this->command(base64_encode($password), "235");
+ }
+
+ protected function crammd5Auth($user, $password)
+ {
+ $result = $this->command("AUTH CRAM-MD5", "334");
+ $challenge = base64_decode($result);
+
+ if (strlen($password) > 64) {
+ $password = pack("H*", md5($password));
+ } elseif (strlen($password) < 64) {
+ $password = str_pad($password, 64, "\000");
+ }
+
+ $k_ipad = substr($password, 0, 64) ^ str_repeat("\066", 64);
+ $k_opad = substr($password, 0, 64) ^ str_repeat("\134", 64);
+ $digest = md5($k_opad . pack("H*", md5($k_ipad . $challenge)));
+
+ $this->command(base64_encode($user . " " . $digest), "235");
+ }
+
+ protected function command($command, $expectedStatus = null)
+ {
+ fwrite($this->smtp, $command . $this->eol);
+
+ if ($expectedStatus === null) {
+ return true;
+ } else {
+ $result = rtrim(fgets($this->smtp));
+ preg_match("/^[0-9]+/", $result, $matches);
+
+ if ($matches[0] === $expectedStatus) {
+ return substr($result, strlen($matches[0]) + 1);
+ } else {
+ $message = "got unexpected response code '{$matches[0]}'.";
+ $exception = new Sabel_Mail_Smtp_Exception($message);
+ $exception->setResponseCode($matches[0]);
+ throw $exception;
+ }
+ }
+ }
+
+ protected function _auth($authMethod)
+ {
+ $method = strtolower($authMethod) . "Auth";
+ if ($this->hasMethod($method)) {
+ $this->$method($this->config["user"], $this->config["password"]);
+ } else {
+ $message = __METHOD__ . "() {$authMethod} is unsupported Authentication method.";
+ throw new Sabel_Mail_Exception($message);
+ }
+ }
+}
diff --git a/sabel/mail/smtp/Exception.php b/sabel/mail/smtp/Exception.php
new file mode 100755
index 0000000..2ff096c
--- /dev/null
+++ b/sabel/mail/smtp/Exception.php
@@ -0,0 +1,25 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Smtp_Exception extends Sabel_Exception_Runtime
+{
+ protected $responseCode = null;
+
+ public function setResponseCode($code)
+ {
+ $this->responseCode = $code;
+ }
+
+ public function getResponseCode()
+ {
+ return $this->responseCode;
+ }
+}
diff --git a/sabel/mail/smtp/exception/AuthFailure.php b/sabel/mail/smtp/exception/AuthFailure.php
new file mode 100755
index 0000000..94bd769
--- /dev/null
+++ b/sabel/mail/smtp/exception/AuthFailure.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Smtp_Exception_AuthFailure extends Sabel_Mail_Smtp_Exception
+{
+
+}
diff --git a/sabel/mail/smtp/exception/ConnectionRefused.php b/sabel/mail/smtp/exception/ConnectionRefused.php
new file mode 100755
index 0000000..19bf92d
--- /dev/null
+++ b/sabel/mail/smtp/exception/ConnectionRefused.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Smtp_Exception_ConnectionRefused extends Sabel_Mail_Smtp_Exception
+{
+
+}
diff --git a/sabel/mail/smtp/exception/RecipientRefused.php b/sabel/mail/smtp/exception/RecipientRefused.php
new file mode 100755
index 0000000..952bc06
--- /dev/null
+++ b/sabel/mail/smtp/exception/RecipientRefused.php
@@ -0,0 +1,15 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Mail_Smtp_Exception_RecipientRefused extends Sabel_Mail_Smtp_Exception
+{
+
+}
diff --git a/sabel/map/Candidate.php b/sabel/map/Candidate.php
new file mode 100755
index 0000000..704311e
--- /dev/null
+++ b/sabel/map/Candidate.php
@@ -0,0 +1,92 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Map_Candidate extends Sabel_Object
+{
+ const MODULE = "module";
+ const CONTROLLER = "controller";
+ const ACTION = "action";
+
+ /**
+ * @var string
+ */
+ protected $name = "";
+
+ /**
+ * @var array
+ */
+ protected $uriParameters = array();
+
+ /**
+ * @var array
+ */
+ protected $destination = array("module" => "", "controller" => "", "action" => "");
+
+ public function __construct($name, $uriParameters)
+ {
+ $this->name = $name;
+
+ $reserved = array("module", "controller", "action");
+ foreach ($uriParameters as $name => $value) {
+ if (in_array($name, $reserved, true)) {
+ $this->destination[$name] = $value;
+ unset($uriParameters[$name]);
+ }
+ }
+
+ $this->uriParameters = array_map("urldecode", $uriParameters);
+ }
+
+ public function getUriParameters()
+ {
+ return $this->uriParameters;
+ }
+
+ public function getDestination()
+ {
+ return new Sabel_Map_Destination($this->destination);
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function uri($param = "")
+ {
+ if ($param === null) {
+ $param = "";
+ } elseif (!is_string($param)) {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ if ($param !== "" && strpos($param, ",") === false && strpos($param, ":") === false) {
+ return ltrim($param, "/");
+ }
+
+ $parameters = array();
+
+ if ($param === "") {
+ $parameters = array_merge($this->destination, $this->uriParameters);
+ } else {
+ foreach (explode(",", $param) as $param) {
+ list ($key, $val) = array_map("trim", explode(":", $param));
+ if ($key === "n") $key = "name";
+ $parameters[$key] = $val;
+ }
+ }
+
+ $name = (isset($parameters["name"])) ? $parameters["name"] : $this->name;
+ $route = Sabel_Map_Configurator::getRoute($name);
+ return $route->createUrl($parameters, array_merge($this->destination, $this->uriParameters));
+ }
+}
diff --git a/sabel/map/Configurator.php b/sabel/map/Configurator.php
new file mode 100755
index 0000000..f1d244d
--- /dev/null
+++ b/sabel/map/Configurator.php
@@ -0,0 +1,96 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+abstract class Sabel_Map_Configurator implements Sabel_Config
+{
+ protected static $routes = array();
+
+ public function route($name)
+ {
+ return self::$routes[$name] = new Sabel_Map_Config_Route($name);
+ }
+
+ public function getValidCandidate($requestUri)
+ {
+ foreach (self::$routes as $route) {
+ if ($parmas = $this->createUriParameter($route, $requestUri)) {
+ return new Sabel_Map_Candidate($route->getName(), $parmas);
+ }
+ }
+
+ return null;
+ }
+
+ protected function createUriParameter($route, $requestUri)
+ {
+ if ($route->getUri() === "*") { // matchall
+ return $route->getDestination();
+ }
+
+ $defaults = $route->getDefaults();
+ $requirements = $route->getRequirements();
+ $regex = $this->convertToRegex($route->getUri(), $defaults, $requirements);
+
+ preg_match_all($regex, $requestUri, $matches);
+ if (empty($matches[0])) return false;
+
+ foreach ($matches as $key => $value) {
+ if (is_int($key)) {
+ unset($matches[$key]);
+ } else {
+ $matches[$key] = $value[0];
+ }
+ }
+
+ foreach ($defaults as $name => $value) {
+ $name = ltrim($name, ":");
+ if ($matches[$name] === "") $matches[$name] = $value;
+ }
+
+ return array_merge($matches, $route->getDestination());
+ }
+
+ protected function convertToRegex($pattern, $defaults, $requirements)
+ {
+ $pattern = strrev($pattern);
+ for ($i = 0, $c = count($defaults); $i < $c; $i++) {
+ $pattern = "?)" . $pattern;
+ $pattern = preg_replace("%(/)([^(])%", '$1($2', $pattern, 1, $count);
+ if ($count === 0) $pattern .= "(";
+ }
+
+ $regex = "@^" . preg_replace('/:(\w+)/', '(?P<$1>[^/]+)', strrev($pattern)) . "$@";
+
+ $any = "(\[\^/\]\+)";
+ foreach ($requirements as $name => $reg) {
+ $regex = preg_replace("@<(" . ltrim($name, ":") . ")>{$any}@", '<$1>' . $reg, $regex);
+ }
+
+ return preg_replace("@<(module|controller|action)>{$any}@", '<$1>\w+', $regex);
+ }
+
+ public static function getRoute($name)
+ {
+ if (isset(self::$routes[$name])) {
+ return self::$routes[$name];
+ } else {
+ return null;
+ }
+ }
+
+ public static function clearRoutes()
+ {
+ self::$routes = array();
+ }
+}
diff --git a/sabel/map/Destination.php b/sabel/map/Destination.php
new file mode 100755
index 0000000..0e4a142
--- /dev/null
+++ b/sabel/map/Destination.php
@@ -0,0 +1,156 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Map_Destination extends Sabel_Object
+{
+ /**
+ * @var string
+ */
+ private $module = "";
+
+ /**
+ * @var string
+ */
+ private $controller = "";
+
+ /**
+ * @var string
+ */
+ private $action = "";
+
+ /**
+ * @param array $destination
+ *
+ * @return boolean
+ */
+ public function __construct(array $destination)
+ {
+ $this->module = $destination["module"];
+ $this->controller = $destination["controller"];
+ $this->action = $destination["action"];
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode(", ", array(
+ "module: " . $this->module,
+ "controller: " . $this->controller,
+ "action: " . $this->action,
+ ));
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasModule()
+ {
+ return ($this->module !== "");
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasController()
+ {
+ return ($this->controller !== "");
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasAction()
+ {
+ return ($this->action !== "");
+ }
+
+ /**
+ * @return string
+ */
+ public function getModule()
+ {
+ return $this->module;
+ }
+
+ /**
+ * @return string
+ */
+ public function getController()
+ {
+ return $this->controller;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAction()
+ {
+ return $this->action;
+ }
+
+ /**
+ * @param string $module
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return void
+ */
+ public function setModule($module)
+ {
+ if (is_string($module)) {
+ $this->module = $module;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * @param string $controller
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return void
+ */
+ public function setController($controller)
+ {
+ if (is_string($controller)) {
+ $this->controller = $controller;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * @param string $action
+ *
+ * @throws Sabel_Exception_InvalidArgument
+ * @return void
+ */
+ public function setAction($action)
+ {
+ if (is_string($action)) {
+ $this->action = $action;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ return array($this->module, $this->controller, $this->action);
+ }
+}
diff --git a/sabel/map/config/Route.php b/sabel/map/config/Route.php
new file mode 100755
index 0000000..2fe9d1d
--- /dev/null
+++ b/sabel/map/config/Route.php
@@ -0,0 +1,163 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Map_Config_Route
+{
+ private $name = "";
+ private $uri = "";
+ private $requirements = array();
+ private $defaults = array();
+
+ private $module = "", $controller = "", $action = "";
+
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ public function uri($uri)
+ {
+ $this->uri = $uri;
+
+ return $this;
+ }
+
+ public function requirements($requirements)
+ {
+ $this->requirements = $requirements;
+
+ return $this;
+ }
+
+ public function defaults($defaults)
+ {
+ $this->defaults = $defaults;
+
+ return $this;
+ }
+
+ public function module($module)
+ {
+ $this->module = $module;
+
+ return $this;
+ }
+
+ public function controller($controller)
+ {
+ $this->controller = $controller;
+
+ return $this;
+ }
+
+ public function action($action)
+ {
+ $this->action = $action;
+
+ return $this;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function getRequirements()
+ {
+ return $this->requirements;
+ }
+
+ public function getDefaults()
+ {
+ return $this->defaults;
+ }
+
+ public function getDestination()
+ {
+ $destination = array();
+
+ if ($this->module !== "") {
+ $destination["module"] = $this->module;
+ }
+
+ if ($this->controller !== "") {
+ $destination["controller"] = $this->controller;
+ }
+
+ if ($this->action !== "") {
+ $destination["action"] = $this->action;
+ }
+
+ return $destination;
+ }
+
+ public function createUrl($params, $currentUris = array())
+ {
+ unset($params["name"]);
+
+ $parts = explode("/", $this->uri);
+ $firstIndex = null;
+
+ foreach ($params as $key => $param) {
+ switch ($key) {
+ case "m":
+ $params["module"] = $param;
+ $key = "module";
+ unset($params["m"]);
+ break;
+
+ case "c":
+ $params["controller"] = $param;
+ $key = "controller";
+ unset($params["c"]);
+ break;
+
+ case "a":
+ $params["action"] = $param;
+ $key = "action";
+ unset($params["a"]);
+ break;
+ }
+
+ if ($firstIndex === null) {
+ $firstIndex = array_search(":" . $key, $parts, true);
+ }
+ }
+
+ $i = 0;
+ $url = array();
+ $defaults = $this->defaults;
+
+ foreach ($parts as $name) {
+ if ($name{0} !== ":") {
+ $url[] = $name;
+ } else {
+ $key = ltrim($name, ":");
+ if (array_isset($key, $params)) {
+ $url[] = $params[$key];
+ } elseif ($firstIndex !== false && $i >= $firstIndex) {
+ if (isset($defaults[$name])) $url[] = $defaults[$name];
+ } elseif (array_isset($key, $currentUris)) {
+ $url[] = $currentUris[$key];
+ }
+
+ $i++;
+ }
+ }
+
+ return implode("/", array_map("urlencode", $url));
+ }
+}
diff --git a/sabel/preference/Backend.php b/sabel/preference/Backend.php
new file mode 100755
index 0000000..7042440
--- /dev/null
+++ b/sabel/preference/Backend.php
@@ -0,0 +1,22 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+interface Sabel_Preference_Backend
+{
+ public function set($key, $value, $type);
+ public function has($key);
+ public function get($key);
+ public function getAll();
+ public function delete($key);
+}
\ No newline at end of file
diff --git a/sabel/preference/Database.php b/sabel/preference/Database.php
new file mode 100755
index 0000000..0ece82d
--- /dev/null
+++ b/sabel/preference/Database.php
@@ -0,0 +1,107 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Preference_Database implements Sabel_Preference_Backend
+{
+ const DEFUALT_NAMESPACE = "default";
+
+ private $config = null;
+ private $model = null;
+ private $namespace = null;
+
+ public function __construct($config = array())
+ {
+ $this->config = $config;
+
+ if (isset($this->config["namespace"])) {
+ $this->namespace = $this->config["namespace"];
+ } else {
+ $this->namespace = self::DEFUALT_NAMESPACE;
+ }
+
+ if (isset($config["model"])) {
+ $this->model = MODEL($config["model"]);
+ } else {
+ $this->model = MODEL("SblPreference");
+ }
+ }
+
+ public function set($key, $value, $type)
+ {
+ $this->model->setCondition("namespace", $this->namespace);
+ $this->model->setCondition("key", $key);
+
+ Sabel_Db_Transaction::activate();
+
+ try {
+ $this->model = $this->model->selectOne();
+
+ $this->model->namespace = $this->namespace;
+ $this->model->key = $key;
+ $this->model->value = $value;
+ $this->model->type = $type;
+
+ $this->model->save();
+
+ Sabel_Db_Transaction::commit();
+ } catch (Exception $e) {
+ Sabel_Db_Transaction::rollback();
+ }
+ }
+
+ public function has($key)
+ {
+ $this->model->setCondition("namespace", $this->namespace);
+ $this->model->setCondition("key", $key);
+
+ return ($this->model->getCount() !== 0);
+ }
+
+ public function get($key)
+ {
+ $this->model->setCondition("namespace", $this->namespace);
+ $this->model->setCondition("key", $key);
+
+ $result = $this->model->selectOne();
+
+ return $result->value;
+ }
+
+ public function delete($key)
+ {
+ $this->model->setCondition("namespace", $this->namespace);
+ $this->model->setCondition("key", $key);
+
+ Sabel_Db_Transaction::activate();
+
+ try {
+ $this->model->delete();
+ Sabel_Db_Transaction::commit();
+ } catch (Exception $e) {
+ Sabel_Db_Transaction::rollback();
+ }
+ }
+
+ public function getAll()
+ {
+ $map = array();
+
+ $this->model->setCondition("namespace", $this->namespace);
+
+ foreach ($this->model->select() as $model) {
+ $map[$model->key] = array("value" => $model->value,
+ "type" => $model->type);
+ }
+
+ return $map;
+ }
+}
diff --git a/sabel/preference/Memcache.php b/sabel/preference/Memcache.php
new file mode 100755
index 0000000..668fb45
--- /dev/null
+++ b/sabel/preference/Memcache.php
@@ -0,0 +1,134 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Preference_Memcache implements Sabel_Preference_Backend
+{
+ const DEFUALT_NAMESPACE = "default";
+
+ const ALL_KEYS = "_PREFERENCE_KYES_";
+
+ private $config = null;
+ private $memcache = null;
+ private $namespace = null;
+
+ public function __construct($config = array())
+ {
+ $this->config = $config;
+
+ if (!extension_loaded("memcache")) {
+ throw new Sabel_Exception_Runtime("memcache extension not loaded.");
+ }
+
+ if (isset($config["namespace"])) {
+ $this->namespace = $config["namespace"];
+ } else {
+ $this->namespace = self::DEFUALT_NAMESPACE;
+ }
+
+ if (isset($config["server"])) {
+ $server = $config["server"];
+ } else {
+ $server = "localhost";
+ }
+
+ if (isset($config["port"])) {
+ $port = $config["port"];
+ } else {
+ $port = 11211;
+ }
+
+ $this->memcache = new Memcache();
+ $this->memcache->addServer($server, $port);
+ }
+
+ public function set($key, $value, $type)
+ {
+ $keys = $this->memcache->get(self::ALL_KEYS);
+
+ if ($keys === false) {
+ $keys = array();
+ }
+
+ $keys[$this->genKey($key)] = $this->genKey($key);
+
+ $this->memcache->set(self::ALL_KEYS, $keys);
+
+ if (Sabel_Preference::TYPE_BOOLEAN == $type) {
+ if ($value === false) {
+ $value = 0;
+ }
+ }
+
+ $this->memcache->set($this->genKey($key), $value);
+ $this->memcache->set($this->genTypeKey($key), $type);
+ }
+
+ public function has($key)
+ {
+ return ($this->memcache->get($this->genKey($key)) !== false);
+ }
+
+ public function get($key)
+ {
+ $value = $this->memcache->get($this->genKey($key));
+ $type = $this->memcache->get($this->genTypeKey($key));
+
+ if ($type === Sabel_Preference::TYPE_BOOLEAN && $value === 0) {
+ return false;
+ }
+
+ return $value;
+ }
+
+ public function delete($key)
+ {
+ $keys = $this->memcache->get(self::ALL_KEYS);
+ unset($keys[$this->genKey($key)]);
+ $this->memcache->set(self::ALL_KEYS, $keys);
+
+ return $this->memcache->delete($this->genKey($key));
+ }
+
+ public function getAll()
+ {
+ $map = array();
+
+ $keys = $this->memcache->get(self::ALL_KEYS);
+
+ foreach ($keys as $key) {
+ if (strpos($key, $this->namespace . "::") !== false) {
+ list($ns, $key) = explode("::", $key);
+
+ $value = $this->memcache->get($this->genKey($key));
+
+ if ($value === "false_false") {
+ $value = false;
+ }
+
+ $map[$key] = array("value" => $value,
+ "type" => $this->memcache->get($this->genTypeKey($key)));
+ }
+ }
+
+ return $map;
+ }
+
+ private function genKey($key)
+ {
+ return $this->namespace . "::" . $key;
+ }
+
+ private function genTypeKey($key)
+ {
+ return $this->genKey($key) . "::type";
+ }
+}
diff --git a/sabel/preference/Xml.php b/sabel/preference/Xml.php
new file mode 100755
index 0000000..8bc3032
--- /dev/null
+++ b/sabel/preference/Xml.php
@@ -0,0 +1,116 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Preference_Xml implements Sabel_Preference_Backend
+{
+ const DEFUALT_FILENAME = "default.xml";
+
+ private $filepath = null;
+
+ private $document;
+ private $rootNode;
+
+ private $config = null;
+
+ public function __construct($config = array())
+ {
+ $this->config = $config;
+
+ if (!defined("RUN_BASE")) {
+ throw new Sabel_Exception_Runtime("RUN_BASE can't be undefined");
+ }
+
+ $preferencesDir = RUN_BASE . DS . "data" . DS . "preferences";
+
+ if (isset($this->config["file"])) {
+ $file = $this->config["file"];
+
+ if (strpos($file, ".") === false) {
+ $file .= ".xml";
+ }
+
+ if (isset($config["absolute"])) {
+ $this->filepath = $file;
+ } else {
+ $this->filepath = $preferencesDir . DS . $file;
+ }
+ }
+
+ if ($this->filepath === null) {
+ $this->filepath = $preferencesDir . DS . "default.xml";
+ }
+
+ if (dirname($this->filepath) !== "." && !is_dir(dirname($this->filepath))) {
+ if (!mkdir(dirname($this->filepath), 0755)) {
+ throw new Sabel_Exception_Runtime("can't make directory " . dirname($this->filepath) . " check configuration");
+ }
+ }
+
+ if (!is_readable($this->filepath)) {
+ if (!touch($this->filepath, 0644)) {
+ throw new Sabel_Exception_Runtime("can't create " . $this->filepath . " check configuration");
+ }
+
+ file_put_contents($this->filepath, ' ');
+ }
+
+ $this->document = Sabel_Xml_Document::create();
+ $this->rootNode = $this->document->load("XML", $this->filepath);
+ }
+
+ public function set($key, $value, $type)
+ {
+ if ($this->rootNode->$key->length === 0) {
+ $this->rootNode->addChild($key)->at("value", $value)->at("type", $type);
+ } else {
+ $this->rootNode->$key->at("value", $value)->at("type", $type);
+ }
+
+ $this->document->save();
+ }
+
+ public function has($key)
+ {
+ if (!isset($this->rootNode->$key)) {
+ return false;
+ }
+
+ return ($this->rootNode->$key->length !== 0);
+ }
+
+ public function get($key)
+ {
+ if ($this->has($key)) {
+ return $this->rootNode->$key->at("value");
+ }
+ }
+
+ public function getAll()
+ {
+ $map = array();
+
+ foreach ($this->rootNode->getChildren() as $child) {
+ $map[$child->tagName] = array("value" => $child->at("value"),
+ "type" => $child->at("type"));
+ }
+
+ return $map;
+ }
+
+ public function delete($key)
+ {
+ if ($this->has($key)) {
+ $this->document->save();
+ return $this->rootNode->$key = null;
+ }
+ }
+}
diff --git a/sabel/reflection/Class.php b/sabel/reflection/Class.php
new file mode 100755
index 0000000..6645618
--- /dev/null
+++ b/sabel/reflection/Class.php
@@ -0,0 +1,95 @@
+
+ * @author Ebine Yutaka
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Reflection_Class extends ReflectionClass
+{
+ protected $annotations = false;
+
+ /**
+ * get annotation of class
+ *
+ * @param string $name annotation name
+ * @return string
+ */
+ public function getAnnotation($name)
+ {
+ $annotations = $this->getAnnotations();
+ return (isset($annotations[$name])) ? $annotations[$name] : null;
+ }
+
+ public function getAnnotations()
+ {
+ if ($this->annotations === false) {
+ $reader = Sabel_Annotation_Reader::create();
+ $this->annotations = $reader->process($this->getDocComment());
+ }
+
+ return $this->annotations;
+ }
+
+ public function hasAnnotation($name)
+ {
+ $annotations = $this->getAnnotations();
+ return isset($annotations[$name]);
+ }
+
+ public function getMethod($method)
+ {
+ return new Sabel_Reflection_Method($this->name, $method);
+ }
+
+ public function getMethods($filter = null)
+ {
+ if ($filter === null) {
+ $filter = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED |
+ ReflectionMethod::IS_PRIVATE | ReflectionMethod::IS_STATIC |
+ ReflectionMethod::IS_ABSTRACT | ReflectionMethod::IS_FINAL;
+ }
+
+ $methods = array();
+ foreach (parent::getMethods($filter) as $method) {
+ $methods[$method->name] = $this->getMethod($method->name);
+ }
+
+ return $methods;
+ }
+
+ public function getMethodAnnotation($name, $annotationName)
+ {
+ return $this->getMethod($name)->getAnnotation($annotationName);
+ }
+
+ public function getProperty($property)
+ {
+ return new Sabel_Reflection_Property($this->name, $property);
+ }
+
+ public function getProperties($filter = null)
+ {
+ if ($filter === null) {
+ $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED |
+ ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_STATIC;
+ }
+
+ $properties = array();
+ foreach (parent::getProperties($filter) as $prop) {
+ $properties[$prop->name] = $this->getProperty($prop->name);
+ }
+
+ return $properties;
+ }
+
+ public function isInstanciatable()
+ {
+ return (!$this->isInterface() && !$this->isAbstract());
+ }
+}
diff --git a/sabel/reflection/Method.php b/sabel/reflection/Method.php
new file mode 100755
index 0000000..73b4bd2
--- /dev/null
+++ b/sabel/reflection/Method.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Reflection_Method extends ReflectionMethod
+{
+ private $annotations = false;
+
+ public function getAnnotation($name)
+ {
+ $annotations = $this->getAnnotations();
+ return (isset($annotations[$name])) ? $annotations[$name] : null;
+ }
+
+ public function getAnnotations()
+ {
+ if ($this->annotations === false) {
+ $reader = Sabel_Annotation_Reader::create();
+ $this->annotations = $reader->process($this->getDocComment());
+ }
+
+ return $this->annotations;
+ }
+
+ public function hasAnnotation($name)
+ {
+ $annotations = $this->getAnnotations();
+ return isset($annotations[$name]);
+ }
+}
diff --git a/sabel/reflection/Property.php b/sabel/reflection/Property.php
new file mode 100755
index 0000000..6aadabb
--- /dev/null
+++ b/sabel/reflection/Property.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Reflection_Property extends ReflectionProperty
+{
+ private $annotations = false;
+
+ public function getAnnotation($name)
+ {
+ $annotations = $this->getAnnotations();
+ return (isset($annotations[$name])) ? $annotations[$name] : null;
+ }
+
+ public function getAnnotations()
+ {
+ if ($this->annotations === false) {
+ $reader = Sabel_Annotation_Reader::create();
+ $this->annotations = $reader->process($this->getDocComment());
+ }
+
+ return $this->annotations;
+ }
+
+ public function hasAnnotation($name)
+ {
+ $annotations = $this->getAnnotations();
+ return isset($annotations[$name]);
+ }
+}
diff --git a/sabel/request/File.php b/sabel/request/File.php
new file mode 100755
index 0000000..f55a006
--- /dev/null
+++ b/sabel/request/File.php
@@ -0,0 +1,23 @@
+getContent();
+ }
+
+ public function isEmpty()
+ {
+ return (is_empty($this->path) || $this->size <= 0);
+ }
+
+ public function getContent()
+ {
+ if (is_empty($this->path)) {
+ return "";
+ } else {
+ return file_get_contents($this->path);
+ }
+ }
+}
diff --git a/sabel/request/Internal.php b/sabel/request/Internal.php
new file mode 100755
index 0000000..b99ef42
--- /dev/null
+++ b/sabel/request/Internal.php
@@ -0,0 +1,129 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Request_Internal extends Sabel_Object
+{
+ /**
+ * @var const Sabel_Request
+ */
+ protected $method = Sabel_Request::GET;
+
+ /**
+ * @var array
+ */
+ protected $values = array();
+
+ /**
+ * @var array
+ */
+ protected $response = array();
+
+ /**
+ * @var boolean
+ */
+ protected $withLayout = false;
+
+ public function __construct($method = Sabel_Request::GET)
+ {
+ $this->method = $method;
+ }
+
+ public function values(array $values)
+ {
+ $this->values = $values;
+
+ return $this;
+ }
+
+ public function method($method)
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ public function withLayout($bool)
+ {
+ $this->withLayout = $bool;
+
+ return $this;
+ }
+
+ public function request($uri, Sabel_Bus_Config $config = null)
+ {
+ if ($config === null) {
+ if (class_exists("Config_Bus", false)) {
+ $config = new Config_Bus();
+ } else {
+ $message = __METHOD__ . "() class Config_Bus not found.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ if (strpos($uri, ":")) {
+ $context = Sabel_Context::getContext();
+ $uri = $context->getCandidate()->uri($uri);
+ } else {
+ $uri = ltrim($uri, "/");
+ }
+
+ $request = new Sabel_Request_Object($uri);
+
+ if (isset($parsedUri["query"])) {
+ parse_str($parsedUri["query"], $get);
+ if ($this->method === Sabel_Request::GET) {
+ $this->values = array_merge($this->values, $get);
+ } else {
+ $request->setGetValues($get);
+ }
+ }
+
+ $currentContext = Sabel_Context::getContext();
+ $currentBus = $currentContext->getBus();
+ $request->method($this->method);
+ $request->values($this->values);
+
+ Sabel_Context::setContext(new Sabel_Context());
+
+ $bus = new Sabel_Bus();
+ $bus->set("request", $request);
+ $bus->set("session", $currentBus->get("session"));
+ $bus->set("NO_LAYOUT", !$this->withLayout);
+ $bus->run($config);
+
+ $this->response["response"] = $bus->get("response");
+ $this->response["result"] = $bus->get("result");
+
+ // restore context.
+ $currentContext->setBus($currentBus);
+ Sabel_Context::setContext($currentContext);
+
+ return $this;
+ }
+
+ public function getResponse()
+ {
+ if (isset($this->response["response"])) {
+ return $this->response["response"];
+ } else {
+ return null;
+ }
+ }
+
+ public function getResult()
+ {
+ if (isset($this->response["result"])) {
+ return $this->response["result"];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/sabel/request/Object.php b/sabel/request/Object.php
new file mode 100755
index 0000000..beb7e02
--- /dev/null
+++ b/sabel/request/Object.php
@@ -0,0 +1,373 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Request_Object extends Sabel_Object implements Sabel_Request
+{
+ /**
+ * @var string
+ */
+ private $uri = "";
+
+ /**
+ * @var const Sabel_Request
+ */
+ private $method = Sabel_Request::GET;
+
+ /**
+ * @var array
+ */
+ private $getValues = array();
+
+ /**
+ * @var array
+ */
+ private $postValues = array();
+
+ /**
+ * @var array
+ */
+ private $parameterValues = array();
+
+ /**
+ * @var Sabel_Request_File[]
+ */
+ private $files = array();
+
+ /**
+ * @var array
+ */
+ private $httpHeaders = array();
+
+ public function __construct($uri = "")
+ {
+ $this->setUri($uri);
+ }
+
+ public function setUri($uri)
+ {
+ $this->uri = ltrim($uri, "/");
+
+ return $this;
+ }
+
+ public function getUri($withQuery = false)
+ {
+ $uri = $this->uri;
+
+ if ($withQuery && ($values = $this->fetchGetValues())) {
+ $uri .= "?" . http_build_query($values);
+ }
+
+ return $uri;
+ }
+
+ /**
+ * get request
+ *
+ * @param string $uri
+ */
+ public function get($uri)
+ {
+ return $this->method(Sabel_Request::GET)->setUri($uri);
+ }
+
+ /**
+ * post request
+ *
+ * @param string $uri
+ */
+ public function post($uri)
+ {
+ return $this->method(Sabel_Request::POST)->setUri($uri);
+ }
+
+ /**
+ * put request
+ *
+ * @param string $uri
+ */
+ public function put($uri)
+ {
+ return $this->method(Sabel_Request::PUT)->setUri($uri);
+ }
+
+ /**
+ * delete request
+ *
+ * @param string $uri
+ */
+ public function delete($uri)
+ {
+ return $this->method(Sabel_Request::DELETE)->setUri($uri);
+ }
+
+ public function method($method)
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ public function isPost()
+ {
+ return ($this->method === Sabel_Request::POST);
+ }
+
+ public function isGet()
+ {
+ return ($this->method === Sabel_Request::GET);
+ }
+
+ public function isPut()
+ {
+ return ($this->method === Sabel_Request::PUT);
+ }
+
+ public function isDelete()
+ {
+ return ($this->method === Sabel_Request::DELETE);
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function value($key, $value)
+ {
+ switch ($this->method) {
+ case (Sabel_Request::GET):
+ $this->setGetValue($key, $value);
+ break;
+ case (Sabel_Request::POST):
+ $this->setPostValue($key, $value);
+ break;
+ }
+
+ return $this;
+ }
+
+ public function values(array $lists)
+ {
+ if ($this->isPost()) {
+ $this->setPostValues(array_merge($this->postValues, $lists));
+ } else {
+ $this->setGetValues(array_merge($this->getValues, $lists));
+ }
+
+ return $this;
+ }
+
+ public function hasValueWithMethod($name)
+ {
+ if ($this->isPost()) {
+ return ($this->hasPostValue($name));
+ } elseif ($this->isGet()) {
+ return ($this->hasGetValue($name));
+ }
+ }
+
+ public function getValueWithMethod($name)
+ {
+ if ($this->hasValueWithMethod($name)) {
+ if ($this->isPost()) {
+ return $this->fetchPostValue($name);
+ } elseif ($this->isGet()) {
+ return $this->fetchGetValue($name);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public function setGetValue($key, $value)
+ {
+ $this->getValues[$key] = ($value === "") ? null : $value;
+ }
+
+ public function setGetValues(array $values)
+ {
+ $this->getValues = $this->toNull($values);
+ }
+
+ public function fetchGetValues()
+ {
+ return $this->getValues;
+ }
+
+ public function hasGetValue($name)
+ {
+ return ($this->fetchGetValue($name) !== null);
+ }
+
+ public function isGetSet($name)
+ {
+ return array_key_exists($name, $this->getValues);
+ }
+
+ public function fetchGetValue($key)
+ {
+ return (isset($this->getValues[$key])) ? $this->getValues[$key] : null;
+ }
+
+ public function setPostValue($key, $value)
+ {
+ $this->postValues[$key] = ($value === "") ? null : $value;
+ }
+
+ public function setPostValues(array $values)
+ {
+ $this->postValues = $this->toNull($values);
+ }
+
+ public function hasPostValue($name)
+ {
+ return ($this->fetchPostValue($name) !== null);
+ }
+
+ public function isPostSet($name)
+ {
+ return array_key_exists($name, $this->postValues);
+ }
+
+ public function fetchPostValue($key)
+ {
+ return (isset($this->postValues[$key])) ? $this->postValues[$key] : null;
+ }
+
+ public function fetchPostValues()
+ {
+ return $this->postValues;
+ }
+
+ public function setParameterValue($key, $value)
+ {
+ $this->parameterValues[$key] = ($value === "") ? null : $value;
+ }
+
+ public function setParameterValues(array $values)
+ {
+ $this->parameterValues = $this->toNull($values);
+ }
+
+ public function fetchParameterValue($key)
+ {
+ return (isset($this->parameterValues[$key])) ? $this->parameterValues[$key] : null;
+ }
+
+ public function fetchParameterValues()
+ {
+ return $this->parameterValues;
+ }
+
+ public function setFile($name, $file)
+ {
+ if ($file instanceof Sabel_Request_File) {
+ $this->files[$name] = $file;
+ } else {
+ $message = __METHOD__ . "() argument 2 must be an instance of Sabel_Request_File.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public function setFiles(array $files)
+ {
+ foreach ($files as $name => $file) {
+ $this->setFile($name, $file);
+ }
+ }
+
+ public function hasFile($name)
+ {
+ return ($this->getFile($name) !== null);
+ }
+
+ public function isFileSet($name)
+ {
+ return array_key_exists($name, $this->files);
+ }
+
+ public function getFile($key)
+ {
+ return (isset($this->files[$key])) ? $this->files[$key] : null;
+ }
+
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ public function find($key)
+ {
+ if (empty($key)) return null;
+
+ $result = null;
+ $values = array(
+ $this->fetchPostValues(),
+ $this->fetchGetValues(),
+ $this->fetchParameterValues()
+ );
+
+ foreach ($values as $value) {
+ if (isset($value[$key])) {
+ if ($result !== null) {
+ $message = __METHOD__ . "() request key overlaps.";
+ throw new Sabel_Exception_Runtime($message);
+ } else {
+ $result = $value[$key];
+ }
+ }
+ }
+
+ return ($result === "") ? null : $result;
+ }
+
+ public function setHttpHeaders(array $headers)
+ {
+ $this->httpHeaders = $headers;
+ }
+
+ public function getHttpHeader($name)
+ {
+ $key = "HTTP_" . strtoupper(str_replace("-", "_", $name));
+ return (isset($this->httpHeaders[$key])) ? $this->httpHeaders[$key] : null;
+ }
+
+ public function getHttpHeaders()
+ {
+ return $this->httpHeaders;
+ }
+
+ public function getExtension()
+ {
+ $parts = explode("/", $this->uri);
+ $lastPart = array_pop($parts);
+
+ if (($pos = strpos($lastPart, ".")) === false) {
+ return "";
+ } else {
+ return substr($lastPart, $pos + 1);
+ }
+ }
+
+ protected function toNull($values)
+ {
+ foreach ($values as $key => $value) {
+ if (is_array($value)) {
+ $values[$key] = $this->toNull($value);
+ } elseif ($value === "") {
+ $values[$key] = null;
+ }
+ }
+
+ return $values;
+ }
+}
diff --git a/sabel/response/Header.php b/sabel/response/Header.php
new file mode 100755
index 0000000..a577142
--- /dev/null
+++ b/sabel/response/Header.php
@@ -0,0 +1,52 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Response_Header
+{
+ protected static $redirectCode = array(300, 301, 302, 303, 305, 307);
+
+ public static function output(Sabel_Response $response)
+ {
+ $headers = self::createHeaders($response);
+ if (!$headers) return array();
+
+ if (strtolower(PHP_SAPI) !== "cli") {
+ array_map("header", $headers);
+
+ if (in_array($response->getStatus()->getCode(), self::$redirectCode, true)) {
+ l("redirect: " . $response->getLocation());
+ }
+ }
+
+ return $headers;
+ }
+
+ protected static function createHeaders(Sabel_Response $response)
+ {
+ $headers = array();
+ $status = $response->getStatus();
+
+ $httpVersion = "HTTP/" . $response->getHttpVersion();
+ $headers[] = $httpVersion . " " . $status->toString();
+
+ if ($response->hasHeaders()) {
+ foreach ($response->getHeaders() as $message => $value) {
+ $headers[] = ucfirst($message) . ": " . $value;
+ }
+ }
+
+ if (in_array($status->getCode(), self::$redirectCode, true)) {
+ $headers[] = "Location: " . $response->getLocation();
+ }
+
+ return $headers;
+ }
+}
diff --git a/sabel/response/Object.php b/sabel/response/Object.php
new file mode 100755
index 0000000..fd307d9
--- /dev/null
+++ b/sabel/response/Object.php
@@ -0,0 +1,169 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Response_Object extends Sabel_Object implements Sabel_Response
+{
+ protected $httpVersion = "1.0";
+ protected $status = null;
+ protected $redirector = null;
+ protected $location = "";
+ protected $headers = array();
+ protected $responses = array();
+
+ protected $statusClass = "Sabel_Response_Status";
+ protected $redirectorClass = "Sabel_Response_Redirector";
+
+ public final function __construct()
+ {
+ $this->setUpStatus();
+ $this->setUpRedirector();
+ }
+
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ public function getRedirector()
+ {
+ return $this->redirector;
+ }
+
+ public function isSuccess()
+ {
+ return $this->status->isSuccess();
+ }
+
+ public function isRedirected()
+ {
+ return ($this->redirector->isRedirected() || $this->status->isRedirect());
+ }
+
+ public function isFailure()
+ {
+ return $this->status->isFailure();
+ }
+
+ public function setHttpVersion($version)
+ {
+ if (is_string($version)) {
+ $this->httpVersion = $version;
+ } else {
+ $message = __METHOD__ . "() argument must be a string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+ }
+
+ public function getHttpVersion()
+ {
+ return $this->httpVersion;
+ }
+
+ public function setResponse($key, $value)
+ {
+ $this->responses[$key] = $value;
+ }
+
+ public function getResponse($key)
+ {
+ if (isset($this->responses[$key])) {
+ return $this->responses[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function setResponses(array $responses)
+ {
+ $this->responses = $responses;
+ }
+
+ public function getResponses()
+ {
+ return $this->responses;
+ }
+
+ public function setHeader($key, $value)
+ {
+ $this->headers[$key] = $value;
+ }
+
+ public function getHeader($key)
+ {
+ if (isset($this->headers[$key])) {
+ return $this->headers[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeaders()
+ {
+ return (count($this->headers) !== 0);
+ }
+
+ public function outputHeader()
+ {
+ return Sabel_Response_Header::output($this);
+ }
+
+ public function expiredCache($expire = 31536000)
+ {
+ $this->setHeader("Expires", date(DATE_RFC822, time() + $expire) . " GMT");
+ $this->setHeader("Last-Modified", date(DATE_RFC822, time() - $expire) . " GMT" );
+ $this->setHeader("Cache-Control", "max-age={$expire}");
+ $this->setHeader("Pragma", "");
+ }
+
+ public function setLocation($location)
+ {
+ $this->location = $location;
+ $this->status->setCode(Sabel_Response::FOUND);
+
+ return $this;
+ }
+
+ public function getLocation()
+ {
+ return $this->location;
+ }
+
+ protected function setUpStatus()
+ {
+ $class = $this->statusClass;
+ $this->status = new $class();
+
+ if (!$this->status instanceof Sabel_Response_Status) {
+ $message = __METHOD__ . "() Status object must be an "
+ . "instance of Sabel_Response_Status.";
+
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ protected function setUpRedirector()
+ {
+ $class = $this->redirectorClass;
+ $this->redirector = new $class();
+
+ if (!$this->redirector instanceof Sabel_Response_Redirector) {
+ $message = __METHOD__ . "() Redirector object must be an "
+ . "instance of Sabel_Response_Redirector.";
+
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+}
diff --git a/sabel/response/Redirector.php b/sabel/response/Redirector.php
new file mode 100755
index 0000000..57d293c
--- /dev/null
+++ b/sabel/response/Redirector.php
@@ -0,0 +1,191 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Response_Redirector extends Sabel_Object
+{
+ /**
+ * @var string
+ */
+ protected $url = "";
+
+ /**
+ * @var string
+ */
+ protected $uri = "";
+
+ /**
+ * @var boolean
+ */
+ protected $redirected = false;
+
+ /**
+ * @var array
+ */
+ protected $parameters = array();
+
+ /**
+ * @var string
+ */
+ protected $flagment = "";
+
+ /**
+ * @return boolean
+ */
+ public function isRedirected()
+ {
+ return $this->redirected;
+ }
+
+ /**
+ * @param string $url
+ *
+ * @return void
+ */
+ public function url($url)
+ {
+ $this->url = $url;
+ $this->redirected = true;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * HTTP Redirect to another location with uri.
+ *
+ * @param string $uri
+ * @param array $parameters
+ * @param string $flagment
+ *
+ * @return string
+ */
+ public function to($uri, $parameters = array(), $flagment = "")
+ {
+ $context = Sabel_Context::getContext();
+
+ return $this->uri($context->getCandidate()->uri($uri), $parameters, $flagment);
+ }
+
+ /**
+ * @param string $uri
+ *
+ * @return void
+ */
+ public function uri($uri, $parameters = array(), $flagment = "")
+ {
+ $this->uri = "/" . ltrim($uri, "/");
+
+ $this->setParameters($parameters);
+ $this->setFlagment($flagment);
+
+ $this->redirected = true;
+
+ return $this->uri;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUri($withParameter = true, $withFlagment = true)
+ {
+ $uri = $this->uri;
+
+ if ($withParameter && !empty($this->parameters)) {
+ $uri .= "?" . http_build_query($this->parameters, "", "&");
+ }
+
+ if ($withFlagment && $this->flagment !== "") {
+ $uri .= "#{$this->flagment}";
+ }
+
+ return $uri;
+ }
+
+ /**
+ * @param array $parameters
+ *
+ * @return self
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->parameters = $parameters;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addParameter($key, $value)
+ {
+ $this->parameters[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasParameters()
+ {
+ return (count($this->parameters) > 0);
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return self
+ */
+ public function deleteParameter($key)
+ {
+ unset($this->parameters[$key]);
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ public function clearParameters()
+ {
+ $this->parameters = array();
+
+ return $this;
+ }
+
+ /**
+ * @param string $flagment
+ *
+ * @return self
+ */
+ public function setFlagment($flagment)
+ {
+ $this->flagment = $flagment;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFlagment()
+ {
+ return $this->flagment;
+ }
+}
diff --git a/sabel/response/Status.php b/sabel/response/Status.php
new file mode 100755
index 0000000..6d288bf
--- /dev/null
+++ b/sabel/response/Status.php
@@ -0,0 +1,121 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Response_Status extends Sabel_Object
+{
+ protected $statuses = array(
+ // 1xx Informational
+ Sabel_Response::COUNTINUE => "Countinue",
+ Sabel_Response::SWITCHING_PROTOCOLS => "Switching Protocols",
+ // 2xx Successful
+ Sabel_Response::OK => "OK",
+ Sabel_Response::CREATED => "Created",
+ Sabel_Response::ACCEPTED => "Accepted",
+ Sabel_Response::NON_AUTHORITATIVE_INFORMATION => "Non-Authoritative Information",
+ Sabel_Response::NO_CONTENT => "No Content",
+ Sabel_Response::RESET_CONTENT => "Reset Content",
+ Sabel_Response::PARTIAL_CONTENT => "Partial Content",
+ Sabel_Response::MULTI_STATUS => "Multi-Status",
+ // 3xx Redirection
+ Sabel_Response::MULTIPLE_CHOICES => "Multiple Choices",
+ Sabel_Response::MOVED_PERMANENTLY => "Moved Permanently",
+ Sabel_Response::FOUND => "Found",
+ Sabel_Response::SEE_OTHER => "See Other",
+ Sabel_Response::NOT_MODIFIED => "Not Modified",
+ Sabel_Response::USE_PROXY => "Use Proxy",
+ // 4xx Client Errors
+ Sabel_Response::BAD_REQUEST => "Bad Request",
+ Sabel_Response::UNAUTHORIZED => "Unauthorized",
+ Sabel_Response::PAYMENT_REQUIRED => "Payment Required",
+ Sabel_Response::FORBIDDEN => "Forbidden",
+ Sabel_Response::NOT_FOUND => "Not Found",
+ Sabel_Response::METHOD_NOT_ALLOWED => "Method Not Allowed",
+ Sabel_Response::NOT_ACCEPTABLE => "Not Acceptable",
+ Sabel_Response::PROXY_AUTHENTICATION_REQUIRED => "Proxy Authentication Required",
+ Sabel_Response::REQUEST_TIMEOUT => "Request Timeout",
+ Sabel_Response::CONFLICT => "Conflict",
+ Sabel_Response::GONE => "Gone",
+ Sabel_Response::LENGTH_REQUIRED => "Length Required",
+ Sabel_Response::PRECONDITION_FAILED => "Precondition Failed",
+ Sabel_Response::REQUEST_ENTITY_TOO_LARGE => "Request Entity Too Large",
+ Sabel_Response::REQUEST_URI_TOO_LONG => "Request-Uri Too Long",
+ Sabel_Response::UNSUPPORTED_MEDIA_TYPE => "Unsupported Media Type",
+ Sabel_Response::REQUESTED_RANGE_NOT_SATISFIABLE => "Requested Range Not Satisfiable",
+ Sabel_Response::EXPECTATION_FAILED => "Expectation Failed",
+ Sabel_Response::IM_A_TEAPOT => "I'm a teapot",
+ // 5xx Server Errors
+ Sabel_Response::INTERNAL_SERVER_ERROR => "Internal Server Error",
+ Sabel_Response::NOT_IMPLEMENTED => "Not Implemented",
+ Sabel_Response::BAD_GATEWAY => "Bad Gateway",
+ Sabel_Response::SERVICE_UNAVAILABLE => "Service Unavailable",
+ Sabel_Response::GATEWAY_TIMEOUT => "Gateway Timeout",
+ Sabel_Response::HTTP_VERSION_NOT_SUPPORTED => "HTTP Version Not Supported",
+ Sabel_Response::VARIANT_ALSO_NEGOTIATES => "Variant Also Negotiates",
+ Sabel_Response::INSUFFICIENT_STORAGE => "Insufficient Storage",
+ Sabel_Response::NOT_EXTENDED => "Not Extended"
+ );
+
+ protected $statusCode = Sabel_Response::OK;
+
+ public function __construct($statusCode = Sabel_Response::OK)
+ {
+ $this->statusCode = $statusCode;
+ }
+
+ public function __toString()
+ {
+ return $this->statusCode . " " . $this->getReason();
+ }
+
+ public function getCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function setCode($statusCode)
+ {
+ $this->statusCode = $statusCode;
+ }
+
+ public function getReason()
+ {
+ if (isset($this->statuses[$this->statusCode])) {
+ return $this->statuses[$this->statusCode];
+ } else {
+ return "";
+ }
+ }
+
+ public function isSuccess()
+ {
+ return ((int)floor($this->statusCode / 100) === 2);
+ }
+
+ public function isRedirect()
+ {
+ return ((int)floor($this->statusCode / 100) === 3);
+ }
+
+ public function isClientError()
+ {
+ return ((int)floor($this->statusCode / 100) === 4);
+ }
+
+ public function isServerError()
+ {
+ return ((int)floor($this->statusCode / 100) === 5);
+ }
+
+ public function isFailure()
+ {
+ return ($this->statusCode >= 400 && $this->statusCode <= 500);
+ }
+}
diff --git a/sabel/rss/Reader.php b/sabel/rss/Reader.php
new file mode 100755
index 0000000..9ed0924
--- /dev/null
+++ b/sabel/rss/Reader.php
@@ -0,0 +1,62 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Rss_Reader
+{
+ public static function loadUrl($url, $xmlConfig = array())
+ {
+ return self::load(file_get_contents($url), $xmlConfig);
+ }
+
+ public static function loadFile($path, $xmlConfig = array())
+ {
+ if (is_file($path) && is_readable($path)) {
+ return self::load(file_get_contents($path), $xmlConfig);
+ } else {
+ $message = __METHOD__ . "() file not found or permission denied.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ public static function loadString($contents, $xmlConfig = array())
+ {
+ return self::load($contents, $xmlConfig);
+ }
+
+ protected static function load($contents, $xmlConfig)
+ {
+ $document = Sabel_Xml_Document::create($xmlConfig);
+ $element = $document->loadXML($contents);
+
+ if ($element === null) {
+ $message = __METHOD__ . "() invalid xml.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ switch (strtolower($element->tagName)) {
+ case "rdf:rdf":
+ return new Sabel_Rss_Reader_Rdf($element);
+ case "rss":
+ return new Sabel_Rss_Reader_Rss($element);
+ break;
+ case "feed":
+ if ($element->getAttribute("version") === "0.3") {
+ return new Sabel_Rss_Reader_Atom03($element);
+ } else {
+ return new Sabel_Rss_Reader_Atom10($element);
+ }
+ break;
+ default:
+ $message = __METHOD__ . "() unknown feed format.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+}
diff --git a/sabel/rss/Writer.php b/sabel/rss/Writer.php
new file mode 100755
index 0000000..a9c69a6
--- /dev/null
+++ b/sabel/rss/Writer.php
@@ -0,0 +1,194 @@
+
+ * @copyright 2004-2008 Mori Reo
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Sabel_Rss_Writer extends Sabel_Object
+{
+ /**
+ * @var string
+ */
+ protected $type = "Rss";
+
+ /**
+ * @var array
+ */
+ protected $info = array(
+ "xmlVersion" => "1.0",
+ "encoding" => "UTF-8",
+ "language" => "en",
+ "home" => "",
+ "image" => array(),
+ "rss" => "",
+ "title" => "",
+ "description" => "",
+ "updated" => "",
+ );
+
+ /**
+ * @var array
+ */
+ protected $items = array();
+
+ /**
+ * @var int
+ */
+ protected $summaryLength = 0;
+
+ public function __construct($type = "Rss")
+ {
+ $type = ucfirst(strtolower($type));
+ $type = str_replace(".", "", $type);
+
+ if ($type === "Rss20") {
+ $this->type = "Rss";
+ } elseif ($type === "Rss10") {
+ $this->type = "Rdf";
+ } elseif (in_array($type, array("Rss", "Rdf", "Atom10", "Atom03"), true)) {
+ $this->type = $type;
+ } else {
+ $message = __METHOD__ . "() '{$type}' is not supported now.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+ }
+
+ /**
+ * @param array $info
+ *
+ * @return self
+ */
+ public function setFeedInfo(array $info)
+ {
+ $this->info = array_merge($this->info, $info);
+
+ return $this;
+ }
+
+ public function setHome($url)
+ {
+ $this->info["home"] = $url;
+
+ return $this;
+ }
+
+ public function setTitle($title)
+ {
+ $this->info["title"] = $title;
+
+ return $this;
+ }
+
+ public function setDescription($description)
+ {
+ $this->info["description"] = $description;
+
+ return $this;
+ }
+
+ public function setImage($imgInfo)
+ {
+ if (is_array($imgInfo)) {
+ $this->info["image"] = $imgInfo;
+ } elseif (is_string($imgInfo)) {
+ $this->info["image"]["src"] = $imgInfo;
+ } else {
+ $message = __METHOD__ . "() argument must be an array or string.";
+ throw new Sabel_Exception_InvalidArgument($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return self
+ */
+ public function addItem(array $item)
+ {
+ if (array_isset("url", $item)) {
+ $item["link"] = $item["url"];
+ }
+
+ if (array_isset("summary", $item)) {
+ $item["description"] = $item["summary"];
+ }
+
+ if (!array_isset("link", $item)) {
+ $message = __METHOD__ . "() empty item url.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ $this->items[] = $item;
+
+ return $this;
+ }
+
+ /**
+ * @param int $length
+ *
+ * @return self
+ */
+ public function setSummaryLength($length)
+ {
+ if (is_natural_number($length)) {
+ $this->summaryLength = $length;
+ } else {
+ $message = __METHOD__ . "() argument must be an integer.";
+ throw new Sabel_Exception_Runtime($message);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return string
+ */
+ public function output($path = null)
+ {
+ $items = $this->items;
+
+ if ($this->summaryLength > 0) {
+ $length = $this->summaryLength;
+ if (extension_loaded("mbstring")) {
+ foreach ($items as &$item) {
+ $item["summary"] = mb_strimwidth($item["content"], 0, $length + 3, "...");
+ }
+ } else {
+ foreach ($items as &$item) {
+ if (strlen($item["content"]) > $length) {
+ $item["summary"] = substr($item["content"], 0, $length - 3) . "...";
+ }
+ }
+ }
+ }
+
+ $info = $this->info;
+ if (!array_isset("home", $info)) {
+ $info["home"] = "http://" . get_server_name() . "/";
+ }
+
+ if (!array_isset("updated", $info) && isset($items[0])) {
+ $info["updated"] = (isset($items[0]["date"])) ? $items[0]["date"] : now();
+ }
+
+ $className = "Sabel_Rss_Writer_" . $this->type;
+ $instance = new $className($info);
+
+ $xml = $instance->build($items);
+
+ if ($path !== null) {
+ file_put_contents($path, $xml);
+ }
+
+ return $xml;
+ }
+}
diff --git a/sabel/rss/reader/Abstract.php b/sabel/rss/reader/Abstract.php
new file mode 100755
index 0000000..9fbec55
--- /dev/null
+++ b/sabel/rss/reader/Abstract.php
@@ -0,0 +1,59 @@
+
+ * @copyright 2004-2008 Mori Reo