From 879fc2fbf174d973d0e22751ae34d0553090a06a Mon Sep 17 00:00:00 2001 From: danneh3826 Date: Sat, 27 Nov 2010 06:30:44 +0800 Subject: [PATCH 01/14] removed debug file - security reasons --- 3.0/modules/transcode/debug.php | 50 --------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 3.0/modules/transcode/debug.php diff --git a/3.0/modules/transcode/debug.php b/3.0/modules/transcode/debug.php deleted file mode 100644 index 7e9d31a9..00000000 --- a/3.0/modules/transcode/debug.php +++ /dev/null @@ -1,50 +0,0 @@ -ffmpeg info"; - $ffmpegPath = whereis("ffmpeg"); - echo "path: " . $ffmpegPath . "
"; - $version = @shell_exec($ffmpegPath . " -version"); $version = explode("\n", $version); $version = $version[0]; $version = explode(" ", $version); $version = $version[1]; - echo "version: " . $version . "
"; - echo "codecs:
" . @shell_exec($ffmpegPath . " -codecs 2>&1") . "

"; - echo "formats:
" . @shell_exec($ffmpegPath . " -formats") . "

"; -} - -function whereis($app) { - $op = @shell_exec("whereis " . $app); - if ($op != "") { - $op = explode(" ", $op); - for ($i = 1; $i < count($op); $i++) { - if (file_exists($op[$i]) && !is_dir($op[$i])) - return $op[$i]; - } - } - return false; -} - -/* -stdClass Object -( - [video] => stdClass Object - ( - [codec] => h264, - [height] => 576 - [width] => 640 - ) - - [audio] => stdClass Object - ( - ) - -) - - */ \ No newline at end of file From 37335dec314a3deb1c5a0f022947dabdb3e29566 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Sun, 28 Nov 2010 10:35:11 +0800 Subject: [PATCH 02/14] Oops, add in security. Only show viewable children. --- 3.1/modules/albumtree/views/albumtree_block.html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3.1/modules/albumtree/views/albumtree_block.html.php b/3.1/modules/albumtree/views/albumtree_block.html.php index 7cf66799..4a73c333 100644 --- a/3.1/modules/albumtree/views/albumtree_block.html.php +++ b/3.1/modules/albumtree/views/albumtree_block.html.php @@ -10,7 +10,7 @@ -children(null, null, array(array("type", "=", "album"))) as $child): ?> +viewable()->children(null, null, array(array("type", "=", "album"))) as $child): ?> From 26dd24e702aa9747067a14ba84efdd709e0d9431 Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sat, 27 Nov 2010 22:30:48 +0800 Subject: [PATCH 03/14] Just a test for cancel button. Need to be polished. --- 3.0/themes/sobriety/css/screen.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/3.0/themes/sobriety/css/screen.css b/3.0/themes/sobriety/css/screen.css index 7418c954..bf527138 100644 --- a/3.0/themes/sobriety/css/screen.css +++ b/3.0/themes/sobriety/css/screen.css @@ -801,6 +801,18 @@ div#g-action-status { height: 8em; } +#g-dialog input[type=submit].submit { + float: right; +} + +#g-dialog a.g-cancel { + float: right; + -moz-appearance: button; + -webkit-appearance: push-button; + /*clear: left;*/ +} + + #g-add-photos-canvas-sd { height: 33px; /*margin-right: 63px;*/ From e7316975d2f2f30e5d14f4f732244e90cdf783f8 Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 07:25:26 +0800 Subject: [PATCH 04/14] Initial commit. There is probably many bugs, but it seems to work... --- 3.0/modules/user_chroot/helpers/MY_access.php | 61 ++++++++++ 3.0/modules/user_chroot/helpers/MY_item.php | 34 ++++++ 3.0/modules/user_chroot/helpers/MY_url.php | 47 ++++++++ .../user_chroot/helpers/user_chroot.php | 41 +++++++ .../user_chroot/helpers/user_chroot_event.php | 112 ++++++++++++++++++ .../helpers/user_chroot_installer.php | 40 +++++++ .../user_chroot/libraries/MY_ORM_MPTT.php | 61 ++++++++++ .../user_chroot/models/user_chroot.php | 22 ++++ 3.0/modules/user_chroot/module.info | 3 + 9 files changed, 421 insertions(+) create mode 100644 3.0/modules/user_chroot/helpers/MY_access.php create mode 100644 3.0/modules/user_chroot/helpers/MY_item.php create mode 100644 3.0/modules/user_chroot/helpers/MY_url.php create mode 100644 3.0/modules/user_chroot/helpers/user_chroot.php create mode 100644 3.0/modules/user_chroot/helpers/user_chroot_event.php create mode 100644 3.0/modules/user_chroot/helpers/user_chroot_installer.php create mode 100644 3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php create mode 100644 3.0/modules/user_chroot/models/user_chroot.php create mode 100644 3.0/modules/user_chroot/module.info diff --git a/3.0/modules/user_chroot/helpers/MY_access.php b/3.0/modules/user_chroot/helpers/MY_access.php new file mode 100644 index 00000000..5f73b4ca --- /dev/null +++ b/3.0/modules/user_chroot/helpers/MY_access.php @@ -0,0 +1,61 @@ +id == identity::active_user()->id && user_chroot::album() ) { + if( $item->left_ptr < user_chroot::album()->left_ptr || user_chroot::album()->right_ptr < $item->right_ptr ) { + return false; + } + } + + return parent::user_can($user, $perm_name, $item); + } +} diff --git a/3.0/modules/user_chroot/helpers/MY_item.php b/3.0/modules/user_chroot/helpers/MY_item.php new file mode 100644 index 00000000..423b8ddc --- /dev/null +++ b/3.0/modules/user_chroot/helpers/MY_item.php @@ -0,0 +1,34 @@ +and_open() + ->and_where("items.left_ptr", ">=", user_chroot::album()->left_ptr) + ->and_where("items.right_ptr", "<=", user_chroot::album()->right_ptr) + ->close(); + } + + return $model; + } +} diff --git a/3.0/modules/user_chroot/helpers/MY_url.php b/3.0/modules/user_chroot/helpers/MY_url.php new file mode 100644 index 00000000..31818fa4 --- /dev/null +++ b/3.0/modules/user_chroot/helpers/MY_url.php @@ -0,0 +1,47 @@ +relative_url().'/'.Router::$current_uri, '/'); + } + + return parent::parse_url(); + } + + static function site($uri = '', $protocol = FALSE) { + if( user_chroot::album() ) { + $uri = preg_replace('#^'.user_chroot::album()->relative_url().'#', '', $uri); + } + + return parent::site($uri, $protocol); + } +} \ No newline at end of file diff --git a/3.0/modules/user_chroot/helpers/user_chroot.php b/3.0/modules/user_chroot/helpers/user_chroot.php new file mode 100644 index 00000000..4d0a8499 --- /dev/null +++ b/3.0/modules/user_chroot/helpers/user_chroot.php @@ -0,0 +1,41 @@ +id); + if( $user_chroot->loaded() && $user_chroot->album_id != 0 ) { + $item = ORM::factory("item", $user_chroot->album_id); + if( $item->loaded() ) { + self::$_album = $item; + } + } + } + + return self::$_album; + } +} \ No newline at end of file diff --git a/3.0/modules/user_chroot/helpers/user_chroot_event.php b/3.0/modules/user_chroot/helpers/user_chroot_event.php new file mode 100644 index 00000000..c43c6dc4 --- /dev/null +++ b/3.0/modules/user_chroot/helpers/user_chroot_event.php @@ -0,0 +1,112 @@ +delete($user->id); + } + + /** + * Called when admin is adding a user + */ + static function user_add_form_admin($user, $form) { + $form->add_user->dropdown("user_chroot") + ->label(t("Root Album")) + ->options(self::createGalleryArray()) + ->selected(0); + } + + /** + * Called after a user has been added + */ + static function user_add_form_admin_completed($user, $form) { + $user_chroot = ORM::factory("user_chroot")->where("id", "=", $user->id)->find(); + $user_chroot->id = $user->id; + $user_chroot->album_id = $form->add_user->user_chroot->value; + $user_chroot->save(); + } + + /** + * Called when admin is editing a user + */ + static function user_edit_form_admin($user, $form) { + $user_chroot = ORM::factory("user_chroot")->where("id", "=", $user->id)->find(); + if ($user_chroot->loaded()) { + $selected = $user_chroot->album_id; + } else { + $selected = 0; + } + $form->edit_user->dropdown("user_chroot") + ->label(t("Root Album")) + ->options(self::createGalleryArray()) + ->selected($selected); + } + + /** + * Called after a user had been edited by the admin + */ + static function user_edit_form_admin_completed($user, $form) { + $user_chroot = ORM::factory("user_chroot")->where("id", "=", $user->id)->find(); + if ($user_chroot->loaded()) { + $user_chroot->album_id = $form->edit_user->user_chroot->value; + } else { + $user_chroot->id = $user->id; + $user_chroot->album_id = $form->edit_user->user_chroot->value; + } + $user_chroot->save(); + } + + + /** + * Creates an array of galleries + */ + static function createGalleryArray() { + $array[0] = "none"; + $root = ORM::factory("item", 1); + self::tree($root, "", $array); + return $array; + } + + /** + * recursive function to build array for drop down list + */ + static function tree($parent, $dashes, &$array) { + if ($parent->id == "1") { + $array[$parent->id] = ORM::factory("item", 1)->title; + } else { + $array[$parent->id] = "$dashes $parent->name"; + } + + $albums = ORM::factory("item") + ->where("parent_id", "=", $parent->id) + ->where("type", "=", "album") + ->order_by("title", "ASC") + ->find_all(); + foreach ($albums as $album) { + self::tree($album, "-$dashes", $array); + } + return; + } +} diff --git a/3.0/modules/user_chroot/helpers/user_chroot_installer.php b/3.0/modules/user_chroot/helpers/user_chroot_installer.php new file mode 100644 index 00000000..ee89fb13 --- /dev/null +++ b/3.0/modules/user_chroot/helpers/user_chroot_installer.php @@ -0,0 +1,40 @@ +query("CREATE TABLE IF NOT EXISTS {user_chroots} ( + `id` int(9) NOT NULL, + `album_id` int(9) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`id`)) + DEFAULT CHARSET=utf8;"); + module::set_version("user_chroot", 1); + } + + /** + * Drops the table of user chroot when the module is uninstalled. + */ + static function uninstall() { + $db = Database::instance(); + $db->query("DROP TABLE IF EXISTS {user_chroots};"); + } +} diff --git a/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php b/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php new file mode 100644 index 00000000..e31124ca --- /dev/null +++ b/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php @@ -0,0 +1,61 @@ +model_name = inflector::singular($this->table_name); + } + + /** + * Return the parent of this node + * + * @return ORM + */ + /*function parent() { + if( user_chroot::album() && user_chroot::album()->id == $this->id ) { + return null; + } else { + return parent::parent(); + } + }*/ + + /** + * Return all the parents of this node, in order from root to this node's immediate parent. + * + * @return array ORM + */ + function parents() { + $select = $this + ->where("left_ptr", "<=", $this->left_ptr) + ->where("right_ptr", ">=", $this->right_ptr) + ->where("id", "<>", $this->id) + ->order_by("left_ptr", "ASC"); + + if( user_chroot::album() ) { + $select->where("left_ptr", ">=", user_chroot::album()->left_ptr); + $select->where("right_ptr", "<=", user_chroot::album()->right_ptr); + } + + return $select->find_all(); + } +} diff --git a/3.0/modules/user_chroot/models/user_chroot.php b/3.0/modules/user_chroot/models/user_chroot.php new file mode 100644 index 00000000..30184597 --- /dev/null +++ b/3.0/modules/user_chroot/models/user_chroot.php @@ -0,0 +1,22 @@ + Date: Sat, 27 Nov 2010 06:44:32 +0800 Subject: [PATCH 05/14] added amazon s3 module --- .../aws_s3/controllers/admin_aws_s3.php | 124 ++ .../aws_s3/helpers/MY_embedlinks_block.php | 10 + .../aws_s3/helpers/MY_embedlinks_theme.php | 13 + 3.0/modules/aws_s3/helpers/MY_item.php | 23 + 3.0/modules/aws_s3/helpers/aws_s3.php | 173 +++ 3.0/modules/aws_s3/helpers/aws_s3_event.php | 34 + .../aws_s3/helpers/aws_s3_installer.php | 40 + 3.0/modules/aws_s3/helpers/aws_s3_task.php | 78 + 3.0/modules/aws_s3/lib/s3.php | 1365 +++++++++++++++++ 3.0/modules/aws_s3/models/MY_Item_Model.php | 40 + 3.0/modules/aws_s3/module.info | 3 + .../aws_s3/views/admin_aws_s3.html.php | 13 + 12 files changed, 1916 insertions(+) create mode 100644 3.0/modules/aws_s3/controllers/admin_aws_s3.php create mode 100644 3.0/modules/aws_s3/helpers/MY_embedlinks_block.php create mode 100644 3.0/modules/aws_s3/helpers/MY_embedlinks_theme.php create mode 100644 3.0/modules/aws_s3/helpers/MY_item.php create mode 100644 3.0/modules/aws_s3/helpers/aws_s3.php create mode 100644 3.0/modules/aws_s3/helpers/aws_s3_event.php create mode 100644 3.0/modules/aws_s3/helpers/aws_s3_installer.php create mode 100644 3.0/modules/aws_s3/helpers/aws_s3_task.php create mode 100644 3.0/modules/aws_s3/lib/s3.php create mode 100644 3.0/modules/aws_s3/models/MY_Item_Model.php create mode 100644 3.0/modules/aws_s3/module.info create mode 100644 3.0/modules/aws_s3/views/admin_aws_s3.html.php diff --git a/3.0/modules/aws_s3/controllers/admin_aws_s3.php b/3.0/modules/aws_s3/controllers/admin_aws_s3.php new file mode 100644 index 00000000..8ce2e7ea --- /dev/null +++ b/3.0/modules/aws_s3/controllers/admin_aws_s3.php @@ -0,0 +1,124 @@ +_get_s3_form(); + + if (request::method() == "post") { + access::verify_csrf(); + + if ($form->validate()) { + module::set_var("aws_s3", "enabled", (isset($_POST['enabled']) ? true : false)); + module::set_var("aws_s3", "access_key", $_POST['access_key']); + module::set_var("aws_s3", "secret_key", $_POST['secret_key']); + module::set_var("aws_s3", "bucket_name", $_POST['bucket_name']); + module::set_var("aws_s3", "g3id", $_POST['g3id']); + + module::set_var("aws_s3", "url_str", $_POST['url_str']); + module::set_var("aws_s3", "sig_exp", $_POST['sig_exp']); + + module::set_var("aws_s3", "use_ssl", (isset($_POST['use_ssl']) ? true : false)); + + if (module::get_var("aws_s3", "enabled") && !module::get_var("aws_s3", "synced", false)) + site_status::warning( + t('Your site has not yet been syncronised with your Amazon S3 bucket. Content will not appear correctly until you perform syncronisation. Fix this now', + array("url" => html::mark_clean(url::site("admin/maintenance/start/aws_s3_task::sync?csrf=__CSRF__"))) + ), "aws_s3_not_synced"); + + + message::success(t("Settings have been saved")); + url::redirect("admin/aws_s3"); + } + else { + message::error(t("There was a problem with the submitted form. Please check your values and try again.")); + } + } + + $v = new Admin_View("admin.html"); + $v->page_title = t("Amazon S3 Configuration"); + $v->content = new View("admin_aws_s3.html"); + $v->content->form = $form; + $v->content->end = ""; + + echo $v; + } + + private function _get_s3_form() { + $form = new Forge("admin/aws_s3", "", "post", array("id" => "g-admin-s3-form")); + + $group = $form->group("aws_s3")->label(t("Amazon S3 Settings")); + + $group ->checkbox("enabled") + ->id("s3-enabled") + ->checked(module::get_var("aws_s3", "enabled")) + ->label("S3 enabled"); + + $group ->input("access_key") + ->id("s3-access-key") + ->label("Access Key ID") + ->value(module::get_var("aws_s3", "access_key")) + ->rules("required") + ->error_messages("required", "This field is required") + ->message('Sign up to Amazon S3'); + + $group ->input("secret_key") + ->id("s3-secret-key") + ->label("Secret Access Key") + ->value(module::get_var("aws_s3", "secret_key")) + ->rules("required") + ->error_messages("required", "This field is required"); + + $group ->input("bucket_name") + ->id("s3-bucket") + ->label("Bucket Name") + ->value(module::get_var("aws_s3", "bucket_name")) + ->rules("required") + ->error_messages("required", "This field is required") + ->message("Note: This module will not create a bucket if it does not already exist. Please ensure you have already created the bucket and the bucket has the correct ACL permissions before continuing."); + + $group ->input("g3id") + ->id("s3-g3id") + ->label("G3 ID") + ->value(module::get_var("aws_s3", "g3id", md5(time()))) + ->rules("required") + ->error_messages("required", "This field is required") + ->message("This field allows for multiple G3 instances running off of a single S3 bucket."); + + $group ->checkbox("use_ssl") + ->id("s3-use-ssl") + ->checked(module::get_var("aws_s3", "use_ssl")) + ->label("Use SSL for S3 transfers"); + + $group = $form->group("cdn_settings")->label(t("CDN Settings")); + + $group ->input("url_str") + ->id("s3-url-str") + ->label("URL String") + ->value(module::get_var("aws_s3", "url_str", "http://{bucket}.s3.amazonaws.com/g3/{guid}/{resource}")) + ->rules("required") + ->message("Configure the URL to access uploaded resources on the CDN. Use the following variables to define and build up the URL:
+• {bucket} - Bucket Name
+• {guid} - Unique identifier for this gallery installation
+• {resource} - The end path to the resource/object"); + + $group ->input("sig_exp") + ->id("sig_exp") + ->label("Private Content Signature Duration") + ->value(module::get_var("aws_s3", "sig_exp", 60)) + ->rules("required") + ->callback("aws_s3::validate_number") + ->error_messages("not_numeric", "The value provided is not numeric. Please enter a number in this field.") + ->message("Set the time in seconds for the generated signature for access to permission-restricted S3 objects

+Note: this module does not yet support the creation of signatures to access private objects on S3 via CloudFront CDN."); + + $form ->submit("save") + ->value("Save Settings"); + + + return $form; + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php b/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php new file mode 100644 index 00000000..fe251b1c --- /dev/null +++ b/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php @@ -0,0 +1,10 @@ +item && $theme->item->view_1 == 1) + parent::get($block_id, $theme); + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/helpers/MY_embedlinks_theme.php b/3.0/modules/aws_s3/helpers/MY_embedlinks_theme.php new file mode 100644 index 00000000..5313cf79 --- /dev/null +++ b/3.0/modules/aws_s3/helpers/MY_embedlinks_theme.php @@ -0,0 +1,13 @@ +item; + if ($item->view_1 == 1) + return parent::photo_bottom($theme); + } + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/helpers/MY_item.php b/3.0/modules/aws_s3/helpers/MY_item.php new file mode 100644 index 00000000..f26aa431 --- /dev/null +++ b/3.0/modules/aws_s3/helpers/MY_item.php @@ -0,0 +1,23 @@ +parent(); + if ($parent->id > 1) { + aws_s3::upload_album_cover($parent); + } + } + + static function remove_album_cover($album) { + parent::remove_album_cover($album); + + if ($album->id > 1) { + aws_s3::remove_album_cover($album); + } + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/helpers/aws_s3.php b/3.0/modules/aws_s3/helpers/aws_s3.php new file mode 100644 index 00000000..9589f495 --- /dev/null +++ b/3.0/modules/aws_s3/helpers/aws_s3.php @@ -0,0 +1,173 @@ + 0) + return $matches[1]; + return false; + } + + static function log($item) { + if (is_string($item) || is_numeric($item)) {} + else + $item = print_r($item, true); + + $fh = fopen(VARPATH . "modules/aws_s3/log/aws_s3-" . date("Y-m-d") . ".log", "a"); + fwrite($fh, date("Y-m-d H:i:s") . ": " . $item . "\n"); + fclose($fh); + } + + static function upload_item($item) { + self::get_s3(); + + $success_fs = S3::putObjectFile(VARPATH . "albums/" . $item->relative_path(), + module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("fs/" . $item->relative_path()), + ($item->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + $success_th = S3::putObjectFile(VARPATH . "thumbs/" . $item->relative_path(), + module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("th/" . $item->relative_path()), + ($item->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + $success_rs = S3::putObjectFile(VARPATH . "resizes/" . $item->relative_path(), + module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("rs/" . $item->relative_path()), + ($item->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + + $success = $success_fs && $success_th && $success_rs; + aws_s3::log("item upload success: " . $success); + } + + static function move_item($old_item, $new_item) { + self::get_s3(); + + S3::copyObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("fs/" . $old_item->relative_path()), + module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("fs/" . $new_item->relative_path()), + ($new_item->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("fs/" . $old_item->relative_path())); + + S3::copyObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("rs/" . $old_item->relative_path()), + module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("rs/" . $new_item->relative_path()), + ($new_item->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("rs/" . $old_item->relative_path())); + + S3::copyObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("th/" . $old_item->relative_path()), + module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("th/" . $new_item->relative_path()), + ($new_item->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("th/" . $old_item->relative_path())); + } + + static function remove_item($item) { + self::get_s3(); + + $success_fs = S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("fs/" . $item->relative_path())); + $success_th = S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("th/" . $item->relative_path())); + $success_rs = S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + self::get_resource_url("rs/" . $item->relative_path())); + + $success = $success_fs && $success_th && $success_rs; + aws_s3::log("s3 delete success: " . $success); + } + + static function upload_album_cover($album) { + self::get_s3(); + + if (file_exists(VARPATH . "resizes/" . $album->relative_path() . "/.album.jpg")) + $success_rs = S3::putObjectFile(VARPATH . "resizes/" . $album->relative_path() . "/.album.jpg", + module::get_var("aws_s3", "bucket_name"), + "g3/" . module::get_var("aws_s3", "g3id") . "/rs/" . $album->relative_path() . "/.album.jpg", + ($album->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + else + $success_rs = true; + + if (file_exists(VARPATH . "thumbs/" . $album->relative_path() . "/.album.jpg")) + $success_th = S3::putObjectFile(VARPATH . "thumbs/" . $album->relative_path() . "/.album.jpg", + module::get_var("aws_s3", "bucket_name"), + "g3/" . module::get_var("aws_s3", "g3id") . "/th/" . $album->relative_path() . "/.album.jpg", + ($album->view_1 ? S3::ACL_PUBLIC_READ : S3::ACL_PRIVATE)); + else + $success_th = true; + + $success = $success_rs && $success_th; + aws_s3::log("album cover upload success: " . $success); + } + + static function remove_album_cover($album) { + self::get_s3(); + + $success_th = S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + "g3/" . module::get_var("aws_s3", "g3id") . "/th/" . $album->relative_path() . "/.album.jpg"); + $success_rs = S3::deleteObject(module::get_var("aws_s3", "bucket_name"), + "g3/" . module::get_var("aws_s3", "g3id") . "/rs/" . $album->relative_path() . "/.album.jpg"); + + $success = $success_rs && $success_th; + aws_s3::log("album cover removal success: " . $success); + } + + static function getAuthenticatedURL($bucket, $uri) { + self::get_s3(); + + return S3::getAuthenticatedURL($bucket, $uri, 60); + } + + static function validate_number($field) { + if (preg_match("/\D/", $field->value)) + $field->add_error("not_numeric", 1); + } + + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/helpers/aws_s3_event.php b/3.0/modules/aws_s3/helpers/aws_s3_event.php new file mode 100644 index 00000000..bb46de80 --- /dev/null +++ b/3.0/modules/aws_s3/helpers/aws_s3_event.php @@ -0,0 +1,34 @@ +get("settings_menu") + ->append( + Menu::factory("link") + ->id("aws_s3_link") + ->label(t("Amazon S3")) + ->url(url::site("admin/aws_s3")) + ); + } + + static function item_created($item) { + if ($item->is_album()) + return true; + + aws_s3::log("Item created - " . $item->id); + aws_s3::upload_item($item); + } + + static function item_deleted($item) { + aws_s3::log("Item deleted - " . $item->id); + aws_s3::remove_item($item); + } + + static function item_moved($new_item, $old_item) { + aws_s3::log("Item moved - " . $item->id); + aws_s3::move_item($old_item, $new_item); + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/helpers/aws_s3_installer.php b/3.0/modules/aws_s3/helpers/aws_s3_installer.php new file mode 100644 index 00000000..75b401b3 --- /dev/null +++ b/3.0/modules/aws_s3/helpers/aws_s3_installer.php @@ -0,0 +1,40 @@ +callback("aws_s3_task::sync") + ->name(t("Syncronise with Amazon S3")) + ->description(t("Syncronise your Gallery 3 data/images with your Amazon S3 bucket")) + ->severity(log::SUCCESS)); + } + + static function sync($task) { + require_once(MODPATH . "aws_s3/lib/s3.php"); + $s3 = new S3(module::get_var("aws_s3", "access_key"), module::get_var("aws_s3", "secret_key")); + + $mode = $task->get("mode", "init"); + switch ($mode) { + case "init": { + aws_s3::log("re-sync task started.."); + batch::start(); + $items = ORM::factory("item")->find_all(); + aws_s3::log("items to sync: " . count($items)); + $task->set("total_count", count($items)); + $task->set("completed", 0); + $task->set("mode", "empty"); + $task->status = "Emptying contents of bucket"; + } break; + case "empty": { // 0 - 10% + aws_s3::log("emptying bucket contents (any files that may already exist in the bucket/prefix path)"); + $bucket = module::get_var("aws_s3", "bucket_name"); + + $resource = aws_s3::get_resource_url(""); + $stuff = array_reverse(S3::getBucket($bucket, $resource)); + foreach ($stuff as $uri => $item) { + aws_s3::log("removing: " . $uri); + S3::deleteObject($bucket, $uri); + } + $task->percent_complete = 10; + $task->set("mode", "upload"); + $task->state = "Commencing upload..."; + } break; + case "upload": { // 10 - 100% + $completed = $task->get("completed", 0); + $items = ORM::factory("item")->find_all(1, $completed); + foreach ($items as $item) { + if ($item->id > 1) { + aws_s3::log("uploading item " . $item->id . " (" . ($completed + 1) . "/" . $task->get("total_count") . ")"); + if ($item->is_album()) + aws_s3::upload_album_cover($item); + else + aws_s3::upload_item($item); + } + $completed++; + } + $task->set("completed", $completed); + $task->percent_complete = round(90 * ($completed / $task->get("total_count"))) + 10; + $task->status = $completed . " of " . $task->get("total_count"). " uploaded."; + + if ($completed == $task->get("total_count")) { + $task->set("mode", "finish"); + } + } break; + case "finish": { + aws_s3::log("completing upload task.."); + $task->percent_complete = 100; + $task->state = "success"; + $task->done = true; + $task->status = "Sync task completed successfully"; + batch::stop(); + module::set_var("aws_s3", "synced", true); + site_status::clear("aws_s3_not_synced"); + } break; + } + + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/lib/s3.php b/3.0/modules/aws_s3/lib/s3.php new file mode 100644 index 00000000..ae9aeb83 --- /dev/null +++ b/3.0/modules/aws_s3/lib/s3.php @@ -0,0 +1,1365 @@ +getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + $results = array(); + if (!isset($rest->body->Buckets)) return $results; + + if ($detailed) { + if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) + $results['owner'] = array( + 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->ID + ); + $results['buckets'] = array(); + foreach ($rest->body->Buckets->Bucket as $b) + $results['buckets'][] = array( + 'name' => (string)$b->Name, 'time' => strtotime((string)$b->CreationDate) + ); + } else + foreach ($rest->body->Buckets->Bucket as $b) $results[] = (string)$b->Name; + + return $results; + } + + + /* + * Get contents for a bucket + * + * If maxKeys is null this method will loop through truncated result sets + * + * @param string $bucket Bucket name + * @param string $prefix Prefix + * @param string $marker Marker (last file listed) + * @param string $maxKeys Max keys (maximum number of keys to return) + * @param string $delimiter Delimiter + * @param boolean $returnCommonPrefixes Set to true to return CommonPrefixes + * @return array | false + */ + public static function getBucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) { + $rest = new S3Request('GET', $bucket, ''); + if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); + if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker); + if ($maxKeys !== null && $maxKeys !== '') $rest->setParameter('max-keys', $maxKeys); + if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); + $response = $rest->getResponse(); + if ($response->error === false && $response->code !== 200) + $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status'); + if ($response->error !== false) { + trigger_error(sprintf("S3::getBucket(): [%s] %s", $response->error['code'], $response->error['message']), E_USER_WARNING); + return false; + } + + $results = array(); + + $nextMarker = null; + if (isset($response->body, $response->body->Contents)) + foreach ($response->body->Contents as $c) { + $results[(string)$c->Key] = array( + 'name' => (string)$c->Key, + 'time' => strtotime((string)$c->LastModified), + 'size' => (int)$c->Size, + 'hash' => substr((string)$c->ETag, 1, -1) + ); + $nextMarker = (string)$c->Key; + } + + if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes)) + foreach ($response->body->CommonPrefixes as $c) + $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix); + + if (isset($response->body, $response->body->IsTruncated) && + (string)$response->body->IsTruncated == 'false') return $results; + + if (isset($response->body, $response->body->NextMarker)) + $nextMarker = (string)$response->body->NextMarker; + + // Loop through truncated results if maxKeys isn't specified + if ($maxKeys == null && $nextMarker !== null && (string)$response->body->IsTruncated == 'true') + do { + $rest = new S3Request('GET', $bucket, ''); + if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix); + $rest->setParameter('marker', $nextMarker); + if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter); + + if (($response = $rest->getResponse(true)) == false || $response->code !== 200) break; + + if (isset($response->body, $response->body->Contents)) + foreach ($response->body->Contents as $c) { + $results[(string)$c->Key] = array( + 'name' => (string)$c->Key, + 'time' => strtotime((string)$c->LastModified), + 'size' => (int)$c->Size, + 'hash' => substr((string)$c->ETag, 1, -1) + ); + $nextMarker = (string)$c->Key; + } + + if ($returnCommonPrefixes && isset($response->body, $response->body->CommonPrefixes)) + foreach ($response->body->CommonPrefixes as $c) + $results[(string)$c->Prefix] = array('prefix' => (string)$c->Prefix); + + if (isset($response->body, $response->body->NextMarker)) + $nextMarker = (string)$response->body->NextMarker; + + } while ($response !== false && (string)$response->body->IsTruncated == 'true'); + + return $results; + } + + + /** + * Put a bucket + * + * @param string $bucket Bucket name + * @param constant $acl ACL flag + * @param string $location Set as "EU" to create buckets hosted in Europe + * @return boolean + */ + public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $location = false) { + $rest = new S3Request('PUT', $bucket, ''); + $rest->setAmzHeader('x-amz-acl', $acl); + + if ($location !== false) { + $dom = new DOMDocument; + $createBucketConfiguration = $dom->createElement('CreateBucketConfiguration'); + $locationConstraint = $dom->createElement('LocationConstraint', strtoupper($location)); + $createBucketConfiguration->appendChild($locationConstraint); + $dom->appendChild($createBucketConfiguration); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + } + $rest = $rest->getResponse(); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::putBucket({$bucket}, {$acl}, {$location}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Delete an empty bucket + * + * @param string $bucket Bucket name + * @return boolean + */ + public static function deleteBucket($bucket) { + $rest = new S3Request('DELETE', $bucket); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 204) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::deleteBucket({$bucket}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Create input info array for putObject() + * + * @param string $file Input file + * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own) + * @return array | false + */ + public static function inputFile($file, $md5sum = true) { + if (!file_exists($file) || !is_file($file) || !is_readable($file)) { + trigger_error('S3::inputFile(): Unable to open input file: '.$file, E_USER_WARNING); + return false; + } + return array('file' => $file, 'size' => filesize($file), + 'md5sum' => $md5sum !== false ? (is_string($md5sum) ? $md5sum : + base64_encode(md5_file($file, true))) : ''); + } + + + /** + * Create input array info for putObject() with a resource + * + * @param string $resource Input resource to read from + * @param integer $bufferSize Input byte size + * @param string $md5sum MD5 hash to send (optional) + * @return array | false + */ + public static function inputResource(&$resource, $bufferSize, $md5sum = '') { + if (!is_resource($resource) || $bufferSize < 0) { + trigger_error('S3::inputResource(): Invalid resource or buffer size', E_USER_WARNING); + return false; + } + $input = array('size' => $bufferSize, 'md5sum' => $md5sum); + $input['fp'] =& $resource; + return $input; + } + + + /** + * Put an object + * + * @param mixed $input Input data + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param array $requestHeaders Array of request headers or content type as a string + * @return boolean + */ + public static function putObject($input, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) { + if ($input === false) return false; + $rest = new S3Request('PUT', $bucket, $uri); + + if (is_string($input)) $input = array( + 'data' => $input, 'size' => strlen($input), + 'md5sum' => base64_encode(md5($input, true)) + ); + + // Data + if (isset($input['fp'])) + $rest->fp =& $input['fp']; + elseif (isset($input['file'])) + $rest->fp = @fopen($input['file'], 'rb'); + elseif (isset($input['data'])) + $rest->data = $input['data']; + + // Content-Length (required) + if (isset($input['size']) && $input['size'] >= 0) + $rest->size = $input['size']; + else { + if (isset($input['file'])) + $rest->size = filesize($input['file']); + elseif (isset($input['data'])) + $rest->size = strlen($input['data']); + } + + // Custom request headers (Content-Type, Content-Disposition, Content-Encoding) + if (is_array($requestHeaders)) + foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v); + elseif (is_string($requestHeaders)) // Support for legacy contentType parameter + $input['type'] = $requestHeaders; + + // Content-Type + if (!isset($input['type'])) { + if (isset($requestHeaders['Content-Type'])) + $input['type'] =& $requestHeaders['Content-Type']; + elseif (isset($input['file'])) + $input['type'] = self::__getMimeType($input['file']); + else + $input['type'] = 'application/octet-stream'; + } + + // We need to post with Content-Length and Content-Type, MD5 is optional + if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false)) { + $rest->setHeader('Content-Type', $input['type']); + if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']); + + $rest->setAmzHeader('x-amz-acl', $acl); + foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v); + $rest->getResponse(); + } else + $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters'); + + if ($rest->response->error === false && $rest->response->code !== 200) + $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); + if ($rest->response->error !== false) { + trigger_error(sprintf("S3::putObject(): [%s] %s", $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Put an object from a file (legacy function) + * + * @param string $file Input file path + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param string $contentType Content type + * @return boolean + */ + public static function putObjectFile($file, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = null) { + return self::putObject(self::inputFile($file), $bucket, $uri, $acl, $metaHeaders, $contentType); + } + + + /** + * Put an object from a string (legacy function) + * + * @param string $string Input data + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Array of x-amz-meta-* headers + * @param string $contentType Content type + * @return boolean + */ + public static function putObjectString($string, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $contentType = 'text/plain') { + return self::putObject($string, $bucket, $uri, $acl, $metaHeaders, $contentType); + } + + + /** + * Get an object + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param mixed $saveTo Filename or resource to write to + * @return mixed + */ + public static function getObject($bucket, $uri, $saveTo = false) { + $rest = new S3Request('GET', $bucket, $uri); + if ($saveTo !== false) { + if (is_resource($saveTo)) + $rest->fp =& $saveTo; + else + if (($rest->fp = @fopen($saveTo, 'wb')) !== false) + $rest->file = realpath($saveTo); + else + $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo); + } + if ($rest->response->error === false) $rest->getResponse(); + + if ($rest->response->error === false && $rest->response->code !== 200) + $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status'); + if ($rest->response->error !== false) { + trigger_error(sprintf("S3::getObject({$bucket}, {$uri}): [%s] %s", + $rest->response->error['code'], $rest->response->error['message']), E_USER_WARNING); + return false; + } + return $rest->response; + } + + + /** + * Get object information + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param boolean $returnInfo Return response information + * @return mixed | false + */ + public static function getObjectInfo($bucket, $uri, $returnInfo = true) { + $rest = new S3Request('HEAD', $bucket, $uri); + $rest = $rest->getResponse(); + if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404)) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getObjectInfo({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false; + } + + + /** + * Copy an object + * + * @param string $bucket Source bucket name + * @param string $uri Source object URI + * @param string $bucket Destination bucket name + * @param string $uri Destination object URI + * @param constant $acl ACL constant + * @param array $metaHeaders Optional array of x-amz-meta-* headers + * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.) + * @return mixed | false + */ + public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $acl = self::ACL_PRIVATE, $metaHeaders = array(), $requestHeaders = array()) { + $rest = new S3Request('PUT', $bucket, $uri); + $rest->setHeader('Content-Length', 0); + foreach ($requestHeaders as $h => $v) $rest->setHeader($h, $v); + foreach ($metaHeaders as $h => $v) $rest->setAmzHeader('x-amz-meta-'.$h, $v); + $rest->setAmzHeader('x-amz-acl', $acl); + $rest->setAmzHeader('x-amz-copy-source', sprintf('/%s/%s', $srcBucket, $srcUri)); + if (sizeof($requestHeaders) > 0 || sizeof($metaHeaders) > 0) + $rest->setAmzHeader('x-amz-metadata-directive', 'REPLACE'); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return isset($rest->body->LastModified, $rest->body->ETag) ? array( + 'time' => strtotime((string)$rest->body->LastModified), + 'hash' => substr((string)$rest->body->ETag, 1, -1) + ) : false; + } + + + /** + * Set logging for a bucket + * + * @param string $bucket Bucket name + * @param string $targetBucket Target bucket (where logs are stored) + * @param string $targetPrefix Log prefix (e,g; domain.com-) + * @return boolean + */ + public static function setBucketLogging($bucket, $targetBucket, $targetPrefix = null) { + // The S3 log delivery group has to be added to the target bucket's ACP + if ($targetBucket !== null && ($acp = self::getAccessControlPolicy($targetBucket, '')) !== false) { + // Only add permissions to the target bucket when they do not exist + $aclWriteSet = false; + $aclReadSet = false; + foreach ($acp['acl'] as $acl) + if ($acl['type'] == 'Group' && $acl['uri'] == 'http://acs.amazonaws.com/groups/s3/LogDelivery') { + if ($acl['permission'] == 'WRITE') $aclWriteSet = true; + elseif ($acl['permission'] == 'READ_ACP') $aclReadSet = true; + } + if (!$aclWriteSet) $acp['acl'][] = array( + 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'WRITE' + ); + if (!$aclReadSet) $acp['acl'][] = array( + 'type' => 'Group', 'uri' => 'http://acs.amazonaws.com/groups/s3/LogDelivery', 'permission' => 'READ_ACP' + ); + if (!$aclReadSet || !$aclWriteSet) self::setAccessControlPolicy($targetBucket, '', $acp); + } + + $dom = new DOMDocument; + $bucketLoggingStatus = $dom->createElement('BucketLoggingStatus'); + $bucketLoggingStatus->setAttribute('xmlns', 'http://s3.amazonaws.com/doc/2006-03-01/'); + if ($targetBucket !== null) { + if ($targetPrefix == null) $targetPrefix = $bucket . '-'; + $loggingEnabled = $dom->createElement('LoggingEnabled'); + $loggingEnabled->appendChild($dom->createElement('TargetBucket', $targetBucket)); + $loggingEnabled->appendChild($dom->createElement('TargetPrefix', $targetPrefix)); + // TODO: Add TargetGrants? + $bucketLoggingStatus->appendChild($loggingEnabled); + } + $dom->appendChild($bucketLoggingStatus); + + $rest = new S3Request('PUT', $bucket, ''); + $rest->setParameter('logging', null); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::setBucketLogging({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get logging status for a bucket + * + * This will return false if logging is not enabled. + * Note: To enable logging, you also need to grant write access to the log group + * + * @param string $bucket Bucket name + * @return array | false + */ + public static function getBucketLogging($bucket) { + $rest = new S3Request('GET', $bucket, ''); + $rest->setParameter('logging', null); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getBucketLogging({$bucket}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + if (!isset($rest->body->LoggingEnabled)) return false; // No logging + return array( + 'targetBucket' => (string)$rest->body->LoggingEnabled->TargetBucket, + 'targetPrefix' => (string)$rest->body->LoggingEnabled->TargetPrefix, + ); + } + + + /** + * Disable bucket logging + * + * @param string $bucket Bucket name + * @return boolean + */ + public static function disableBucketLogging($bucket) { + return self::setBucketLogging($bucket, null); + } + + + /** + * Get a bucket's location + * + * @param string $bucket Bucket name + * @return string | false + */ + public static function getBucketLocation($bucket) { + $rest = new S3Request('GET', $bucket, ''); + $rest->setParameter('location', null); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getBucketLocation({$bucket}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return (isset($rest->body[0]) && (string)$rest->body[0] !== '') ? (string)$rest->body[0] : 'US'; + } + + + /** + * Set object or bucket Access Control Policy + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy) + * @return boolean + */ + public static function setAccessControlPolicy($bucket, $uri = '', $acp = array()) { + $dom = new DOMDocument; + $dom->formatOutput = true; + $accessControlPolicy = $dom->createElement('AccessControlPolicy'); + $accessControlList = $dom->createElement('AccessControlList'); + + // It seems the owner has to be passed along too + $owner = $dom->createElement('Owner'); + $owner->appendChild($dom->createElement('ID', $acp['owner']['id'])); + $owner->appendChild($dom->createElement('DisplayName', $acp['owner']['name'])); + $accessControlPolicy->appendChild($owner); + + foreach ($acp['acl'] as $g) { + $grant = $dom->createElement('Grant'); + $grantee = $dom->createElement('Grantee'); + $grantee->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + if (isset($g['id'])) { // CanonicalUser (DisplayName is omitted) + $grantee->setAttribute('xsi:type', 'CanonicalUser'); + $grantee->appendChild($dom->createElement('ID', $g['id'])); + } elseif (isset($g['email'])) { // AmazonCustomerByEmail + $grantee->setAttribute('xsi:type', 'AmazonCustomerByEmail'); + $grantee->appendChild($dom->createElement('EmailAddress', $g['email'])); + } elseif ($g['type'] == 'Group') { // Group + $grantee->setAttribute('xsi:type', 'Group'); + $grantee->appendChild($dom->createElement('URI', $g['uri'])); + } + $grant->appendChild($grantee); + $grant->appendChild($dom->createElement('Permission', $g['permission'])); + $accessControlList->appendChild($grant); + } + + $accessControlPolicy->appendChild($accessControlList); + $dom->appendChild($accessControlPolicy); + + $rest = new S3Request('PUT', $bucket, $uri); + $rest->setParameter('acl', null); + $rest->data = $dom->saveXML(); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::setAccessControlPolicy({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get object or bucket Access Control Policy + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @return mixed | false + */ + public static function getAccessControlPolicy($bucket, $uri = '') { + $rest = new S3Request('GET', $bucket, $uri); + $rest->setParameter('acl', null); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getAccessControlPolicy({$bucket}, {$uri}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + + $acp = array(); + if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName)) { + $acp['owner'] = array( + 'id' => (string)$rest->body->Owner->ID, 'name' => (string)$rest->body->Owner->DisplayName + ); + } + if (isset($rest->body->AccessControlList)) { + $acp['acl'] = array(); + foreach ($rest->body->AccessControlList->Grant as $grant) { + foreach ($grant->Grantee as $grantee) { + if (isset($grantee->ID, $grantee->DisplayName)) // CanonicalUser + $acp['acl'][] = array( + 'type' => 'CanonicalUser', + 'id' => (string)$grantee->ID, + 'name' => (string)$grantee->DisplayName, + 'permission' => (string)$grant->Permission + ); + elseif (isset($grantee->EmailAddress)) // AmazonCustomerByEmail + $acp['acl'][] = array( + 'type' => 'AmazonCustomerByEmail', + 'email' => (string)$grantee->EmailAddress, + 'permission' => (string)$grant->Permission + ); + elseif (isset($grantee->URI)) // Group + $acp['acl'][] = array( + 'type' => 'Group', + 'uri' => (string)$grantee->URI, + 'permission' => (string)$grant->Permission + ); + else continue; + } + } + } + return $acp; + } + + + /** + * Delete an object + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @return boolean + */ + public static function deleteObject($bucket, $uri) { + $rest = new S3Request('DELETE', $bucket, $uri); + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 204) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::deleteObject(): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get a query string authenticated URL + * + * @param string $bucket Bucket name + * @param string $uri Object URI + * @param integer $lifetime Lifetime in seconds + * @param boolean $hostBucket Use the bucket name as the hostname + * @param boolean $https Use HTTPS ($hostBucket should be false for SSL verification) + * @return string + */ + public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) { + $expires = time() + $lifetime; + $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea) + return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&Expires=%u&Signature=%s', + $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires, + urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}"))); + } + + /** + * Get upload POST parameters for form uploads + * + * @param string $bucket Bucket name + * @param string $uriPrefix Object URI prefix + * @param constant $acl ACL constant + * @param integer $lifetime Lifetime in seconds + * @param integer $maxFileSize Maximum filesize in bytes (default 5MB) + * @param string $successRedirect Redirect URL or 200 / 201 status code + * @param array $amzHeaders Array of x-amz-meta-* headers + * @param array $headers Array of request headers or content type as a string + * @param boolean $flashVars Includes additional "Filename" variable posted by Flash + * @return object + */ + public static function getHttpUploadPostParams($bucket, $uriPrefix = '', $acl = self::ACL_PRIVATE, $lifetime = 3600, $maxFileSize = 5242880, $successRedirect = "201", $amzHeaders = array(), $headers = array(), $flashVars = false) { + // Create policy object + $policy = new stdClass; + $policy->expiration = gmdate('Y-m-d\TH:i:s\Z', (time() + $lifetime)); + $policy->conditions = array(); + $obj = new stdClass; $obj->bucket = $bucket; array_push($policy->conditions, $obj); + $obj = new stdClass; $obj->acl = $acl; array_push($policy->conditions, $obj); + + $obj = new stdClass; // 200 for non-redirect uploads + if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201))) + $obj->success_action_status = (string)$successRedirect; + else // URL + $obj->success_action_redirect = $successRedirect; + array_push($policy->conditions, $obj); + + array_push($policy->conditions, array('starts-with', '$key', $uriPrefix)); + if ($flashVars) array_push($policy->conditions, array('starts-with', '$Filename', '')); + foreach (array_keys($headers) as $headerKey) + array_push($policy->conditions, array('starts-with', '$'.$headerKey, '')); + foreach ($amzHeaders as $headerKey => $headerVal) { + $obj = new stdClass; $obj->{$headerKey} = (string)$headerVal; array_push($policy->conditions, $obj); + } + array_push($policy->conditions, array('content-length-range', 0, $maxFileSize)); + $policy = base64_encode(str_replace('\/', '/', json_encode($policy))); + + // Create parameters + $params = new stdClass; + $params->AWSAccessKeyId = self::$__accessKey; + $params->key = $uriPrefix.'${filename}'; + $params->acl = $acl; + $params->policy = $policy; unset($policy); + $params->signature = self::__getHash($params->policy); + if (is_numeric($successRedirect) && in_array((int)$successRedirect, array(200, 201))) + $params->success_action_status = (string)$successRedirect; + else + $params->success_action_redirect = $successRedirect; + foreach ($headers as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal; + foreach ($amzHeaders as $headerKey => $headerVal) $params->{$headerKey} = (string)$headerVal; + return $params; + } + + /** + * Create a CloudFront distribution + * + * @param string $bucket Bucket name + * @param boolean $enabled Enabled (true/false) + * @param array $cnames Array containing CNAME aliases + * @param string $comment Use the bucket name as the hostname + * @return array | false + */ + public static function createDistribution($bucket, $enabled = true, $cnames = array(), $comment = '') { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('POST', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com'); + $rest->data = self::__getCloudFrontDistributionConfigXML($bucket.'.s3.amazonaws.com', $enabled, $comment, (string)microtime(true), $cnames); + $rest->size = strlen($rest->data); + $rest->setHeader('Content-Type', 'application/xml'); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 201) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::createDistribution({$bucket}, ".(int)$enabled.", '$comment'): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } elseif ($rest->body instanceof SimpleXMLElement) + return self::__parseCloudFrontDistributionConfig($rest->body); + return false; + } + + + /** + * Get CloudFront distribution info + * + * @param string $distributionId Distribution ID from listDistributions() + * @return array | false + */ + public static function getDistribution($distributionId) { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('GET', '', '2008-06-30/distribution/'.$distributionId, 'cloudfront.amazonaws.com'); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::getDistribution($distributionId): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } elseif ($rest->body instanceof SimpleXMLElement) { + $dist = self::__parseCloudFrontDistributionConfig($rest->body); + $dist['hash'] = $rest->headers['hash']; + return $dist; + } + return false; + } + + + /** + * Update a CloudFront distribution + * + * @param array $dist Distribution array info identical to output of getDistribution() + * @return array | false + */ + public static function updateDistribution($dist) { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('PUT', '', '2008-06-30/distribution/'.$dist['id'].'/config', 'cloudfront.amazonaws.com'); + $rest->data = self::__getCloudFrontDistributionConfigXML($dist['origin'], $dist['enabled'], $dist['comment'], $dist['callerReference'], $dist['cnames']); + $rest->size = strlen($rest->data); + $rest->setHeader('If-Match', $dist['hash']); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::updateDistribution({$dist['id']}, ".(int)$enabled.", '$comment'): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } else { + $dist = self::__parseCloudFrontDistributionConfig($rest->body); + $dist['hash'] = $rest->headers['hash']; + return $dist; + } + return false; + } + + + /** + * Delete a CloudFront distribution + * + * @param array $dist Distribution array info identical to output of getDistribution() + * @return boolean + */ + public static function deleteDistribution($dist) { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('DELETE', '', '2008-06-30/distribution/'.$dist['id'], 'cloudfront.amazonaws.com'); + $rest->setHeader('If-Match', $dist['hash']); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 204) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::deleteDistribution({$dist['id']}): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } + return true; + } + + + /** + * Get a list of CloudFront distributions + * + * @return array + */ + public static function listDistributions() { + self::$useSSL = true; // CloudFront requires SSL + $rest = new S3Request('GET', '', '2008-06-30/distribution', 'cloudfront.amazonaws.com'); + $rest = self::__getCloudFrontResponse($rest); + + if ($rest->error === false && $rest->code !== 200) + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + if ($rest->error !== false) { + trigger_error(sprintf("S3::listDistributions(): [%s] %s", + $rest->error['code'], $rest->error['message']), E_USER_WARNING); + return false; + } elseif ($rest->body instanceof SimpleXMLElement && isset($rest->body->DistributionSummary)) { + $list = array(); + if (isset($rest->body->Marker, $rest->body->MaxItems, $rest->body->IsTruncated)) { + //$info['marker'] = (string)$rest->body->Marker; + //$info['maxItems'] = (int)$rest->body->MaxItems; + //$info['isTruncated'] = (string)$rest->body->IsTruncated == 'true' ? true : false; + } + foreach ($rest->body->DistributionSummary as $summary) { + $list[(string)$summary->Id] = self::__parseCloudFrontDistributionConfig($summary); + } + return $list; + } + return array(); + } + + + /** + * Get a DistributionConfig DOMDocument + * + * @internal Used to create XML in createDistribution() and updateDistribution() + * @param string $bucket Origin bucket + * @param boolean $enabled Enabled (true/false) + * @param string $comment Comment to append + * @param string $callerReference Caller reference + * @param array $cnames Array of CNAME aliases + * @return string + */ + private static function __getCloudFrontDistributionConfigXML($bucket, $enabled, $comment, $callerReference = '0', $cnames = array()) { + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $distributionConfig = $dom->createElement('DistributionConfig'); + $distributionConfig->setAttribute('xmlns', 'http://cloudfront.amazonaws.com/doc/2008-06-30/'); + $distributionConfig->appendChild($dom->createElement('Origin', $bucket)); + $distributionConfig->appendChild($dom->createElement('CallerReference', $callerReference)); + foreach ($cnames as $cname) + $distributionConfig->appendChild($dom->createElement('CNAME', $cname)); + if ($comment !== '') $distributionConfig->appendChild($dom->createElement('Comment', $comment)); + $distributionConfig->appendChild($dom->createElement('Enabled', $enabled ? 'true' : 'false')); + $dom->appendChild($distributionConfig); + return $dom->saveXML(); + } + + + /** + * Parse a CloudFront distribution config + * + * @internal Used to parse the CloudFront DistributionConfig node to an array + * @param object &$node DOMNode + * @return array + */ + private static function __parseCloudFrontDistributionConfig(&$node) { + $dist = array(); + if (isset($node->Id, $node->Status, $node->LastModifiedTime, $node->DomainName)) { + $dist['id'] = (string)$node->Id; + $dist['status'] = (string)$node->Status; + $dist['time'] = strtotime((string)$node->LastModifiedTime); + $dist['domain'] = (string)$node->DomainName; + } + if (isset($node->CallerReference)) + $dist['callerReference'] = (string)$node->CallerReference; + if (isset($node->Comment)) + $dist['comment'] = (string)$node->Comment; + if (isset($node->Enabled, $node->Origin)) { + $dist['origin'] = (string)$node->Origin; + $dist['enabled'] = (string)$node->Enabled == 'true' ? true : false; + } elseif (isset($node->DistributionConfig)) { + $dist = array_merge($dist, self::__parseCloudFrontDistributionConfig($node->DistributionConfig)); + } + if (isset($node->CNAME)) { + $dist['cnames'] = array(); + foreach ($node->CNAME as $cname) $dist['cnames'][(string)$cname] = (string)$cname; + } + return $dist; + } + + + /** + * Grab CloudFront response + * + * @internal Used to parse the CloudFront S3Request::getResponse() output + * @param object &$rest S3Request instance + * @return object + */ + private static function __getCloudFrontResponse(&$rest) { + $rest->getResponse(); + if ($rest->response->error === false && isset($rest->response->body) && + is_string($rest->response->body) && substr($rest->response->body, 0, 5) == 'response->body = simplexml_load_string($rest->response->body); + // Grab CloudFront errors + if (isset($rest->response->body->Error, $rest->response->body->Error->Code, + $rest->response->body->Error->Message)) { + $rest->response->error = array( + 'code' => (string)$rest->response->body->Error->Code, + 'message' => (string)$rest->response->body->Error->Message + ); + unset($rest->response->body); + } + } + return $rest->response; + } + + + /** + * Get MIME type for file + * + * @internal Used to get mime types + * @param string &$file File path + * @return string + */ + public static function __getMimeType(&$file) { + $type = false; + // Fileinfo documentation says fileinfo_open() will use the + // MAGIC env var for the magic file + if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) && + ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false) { + if (($type = finfo_file($finfo, $file)) !== false) { + // Remove the charset and grab the last content-type + $type = explode(' ', str_replace('; charset=', ';charset=', $type)); + $type = array_pop($type); + $type = explode(';', $type); + $type = trim(array_shift($type)); + } + finfo_close($finfo); + + // If anyone is still using mime_content_type() + } elseif (function_exists('mime_content_type')) + $type = trim(mime_content_type($file)); + + if ($type !== false && strlen($type) > 0) return $type; + + // Otherwise do it the old fashioned way + static $exts = array( + 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', + 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'ico' => 'image/x-icon', + 'swf' => 'application/x-shockwave-flash', 'pdf' => 'application/pdf', + 'zip' => 'application/zip', 'gz' => 'application/x-gzip', + 'tar' => 'application/x-tar', 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', 'txt' => 'text/plain', + 'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', + 'css' => 'text/css', 'js' => 'text/javascript', + 'xml' => 'text/xml', 'xsl' => 'application/xsl+xml', + 'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav', + 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', + 'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php' + ); + $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION)); + return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream'; + } + + + /** + * Generate the auth string: "AWS AccessKey:Signature" + * + * @internal Used by S3Request::getResponse() + * @param string $string String to sign + * @return string + */ + public static function __getSignature($string) { + return 'AWS '.self::$__accessKey.':'.self::__getHash($string); + } + + + /** + * Creates a HMAC-SHA1 hash + * + * This uses the hash extension if loaded + * + * @internal Used by __getSignature() + * @param string $string String to sign + * @return string + */ + private static function __getHash($string) { + return base64_encode(extension_loaded('hash') ? + hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1( + (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . + pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^ + (str_repeat(chr(0x36), 64))) . $string))))); + } + +} + +final class S3Request { + private $verb, $bucket, $uri, $resource = '', $parameters = array(), + $amzHeaders = array(), $headers = array( + 'Host' => '', 'Date' => '', 'Content-MD5' => '', 'Content-Type' => '' + ); + public $fp = false, $size = 0, $data = false, $response; + + + /** + * Constructor + * + * @param string $verb Verb + * @param string $bucket Bucket name + * @param string $uri Object URI + * @return mixed + */ + function __construct($verb, $bucket = '', $uri = '', $defaultHost = 's3.amazonaws.com') { + $this->verb = $verb; + $this->bucket = strtolower($bucket); + $this->uri = $uri !== '' ? '/'.str_replace('%2F', '/', rawurlencode($uri)) : '/'; + + if ($this->bucket !== '') { + $this->headers['Host'] = $this->bucket.'.'.$defaultHost; + $this->resource = '/'.$this->bucket.$this->uri; + } else { + $this->headers['Host'] = $defaultHost; + //$this->resource = strlen($this->uri) > 1 ? '/'.$this->bucket.$this->uri : $this->uri; + $this->resource = $this->uri; + } + $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); + + $this->response = new STDClass; + $this->response->error = false; + } + + + /** + * Set request parameter + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function setParameter($key, $value) { + $this->parameters[$key] = $value; + } + + + /** + * Set request header + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function setHeader($key, $value) { + $this->headers[$key] = $value; + } + + + /** + * Set x-amz-meta-* header + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function setAmzHeader($key, $value) { + $this->amzHeaders[$key] = $value; + } + + + /** + * Get the S3 response + * + * @return object | false + */ + public function getResponse() { + $query = ''; + if (sizeof($this->parameters) > 0) { + $query = substr($this->uri, -1) !== '?' ? '?' : '&'; + foreach ($this->parameters as $var => $value) + if ($value == null || $value == '') $query .= $var.'&'; + // Parameters should be encoded (thanks Sean O'Dea) + else $query .= $var.'='.rawurlencode($value).'&'; + $query = substr($query, 0, -1); + $this->uri .= $query; + + if (array_key_exists('acl', $this->parameters) || + array_key_exists('location', $this->parameters) || + array_key_exists('torrent', $this->parameters) || + array_key_exists('logging', $this->parameters)) + $this->resource .= $query; + } + $url = ((S3::$useSSL && extension_loaded('openssl')) ? + 'https://':'http://').$this->headers['Host'].$this->uri; + //var_dump($this->bucket, $this->uri, $this->resource, $url); + + // Basic setup + $curl = curl_init(); + curl_setopt($curl, CURLOPT_USERAGENT, 'S3/php'); + + if (S3::$useSSL) { + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1); + } + + curl_setopt($curl, CURLOPT_URL, $url); + + // Headers + $headers = array(); $amz = array(); + foreach ($this->amzHeaders as $header => $value) + if (strlen($value) > 0) $headers[] = $header.': '.$value; + foreach ($this->headers as $header => $value) + if (strlen($value) > 0) $headers[] = $header.': '.$value; + + // Collect AMZ headers for signature + foreach ($this->amzHeaders as $header => $value) + if (strlen($value) > 0) $amz[] = strtolower($header).':'.$value; + + // AMZ headers must be sorted + if (sizeof($amz) > 0) { + sort($amz); + $amz = "\n".implode("\n", $amz); + } else $amz = ''; + + // Authorization string (CloudFront stringToSign should only contain a date) + $headers[] = 'Authorization: ' . S3::__getSignature( + $this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] : + $this->verb."\n".$this->headers['Content-MD5']."\n". + $this->headers['Content-Type']."\n".$this->headers['Date'].$amz."\n".$this->resource + ); + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); + curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); + curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback')); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + + // Request types + switch ($this->verb) { + case 'GET': break; + case 'PUT': case 'POST': // POST only used for CloudFront + if ($this->fp !== false) { + curl_setopt($curl, CURLOPT_PUT, true); + curl_setopt($curl, CURLOPT_INFILE, $this->fp); + if ($this->size >= 0) + curl_setopt($curl, CURLOPT_INFILESIZE, $this->size); + } elseif ($this->data !== false) { + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); + curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data); + if ($this->size >= 0) + curl_setopt($curl, CURLOPT_BUFFERSIZE, $this->size); + } else + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); + break; + case 'HEAD': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD'); + curl_setopt($curl, CURLOPT_NOBODY, true); + break; + case 'DELETE': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); + break; + default: break; + } + + // Execute, grab errors + if (curl_exec($curl)) + $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + else + $this->response->error = array( + 'code' => curl_errno($curl), + 'message' => curl_error($curl), + 'resource' => $this->resource + ); + + @curl_close($curl); + + // Parse body into XML + if ($this->response->error === false && isset($this->response->headers['type']) && + $this->response->headers['type'] == 'application/xml' && isset($this->response->body)) { + $this->response->body = simplexml_load_string($this->response->body); + + // Grab S3 errors + if (!in_array($this->response->code, array(200, 204)) && + isset($this->response->body->Code, $this->response->body->Message)) { + $this->response->error = array( + 'code' => (string)$this->response->body->Code, + 'message' => (string)$this->response->body->Message + ); + if (isset($this->response->body->Resource)) + $this->response->error['resource'] = (string)$this->response->body->Resource; + unset($this->response->body); + } + } + + // Clean up file resources + if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp); + + return $this->response; + } + + + /** + * CURL write callback + * + * @param resource &$curl CURL resource + * @param string &$data Data + * @return integer + */ + private function __responseWriteCallback(&$curl, &$data) { + if ($this->response->code == 200 && $this->fp !== false) + return fwrite($this->fp, $data); + else + $this->response->body .= $data; + return strlen($data); + } + + + /** + * CURL header callback + * + * @param resource &$curl CURL resource + * @param string &$data Data + * @return integer + */ + private function __responseHeaderCallback(&$curl, &$data) { + if (($strlen = strlen($data)) <= 2) return $strlen; + if (substr($data, 0, 4) == 'HTTP') + $this->response->code = (int)substr($data, 9, 3); + else { + list($header, $value) = explode(': ', trim($data), 2); + if ($header == 'Last-Modified') + $this->response->headers['time'] = strtotime($value); + elseif ($header == 'Content-Length') + $this->response->headers['size'] = (int)$value; + elseif ($header == 'Content-Type') + $this->response->headers['type'] = $value; + elseif ($header == 'ETag') + $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value; + elseif (preg_match('/^x-amz-meta-.*$/', $header)) + $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value; + } + return $strlen; + } + +} diff --git a/3.0/modules/aws_s3/models/MY_Item_Model.php b/3.0/modules/aws_s3/models/MY_Item_Model.php new file mode 100644 index 00000000..4cacc2fc --- /dev/null +++ b/3.0/modules/aws_s3/models/MY_Item_Model.php @@ -0,0 +1,40 @@ +is_photo()) { + return aws_s3::generate_url("th/" . $this->relative_path(), ($this->view_1 == 1 ? false : true), $this->updated); + } + else if ($this->is_album() && $this->id > 1) { + return aws_s3::generate_url("th/" . $this->relative_path() . "/.album.jpg", ($this->view_1 == 1 ? false : true), $this->updated); + } + else if ($this->is_movie()) { + $relative_path = preg_replace("/...$/", "jpg", $this->relative_path()); + return aws_s3::generate_url("th/" . $relative_path, ($this->view_1 == 1 ? false : true), $this->updated); + } + } + + public function file_url($full_uri=false) { + if (!module::get_var("aws_s3", "enabled")) + return parent::file_url($full_uri); + + return aws_s3::generate_url("fs/" . $this->relative_path(), ($this->view_1 == 1 ? false : true), $this->updated); + } + + public function resize_url($full_uri=false) { + if (!module::get_var("aws_s3", "enabled")) + return parent::resize_url($full_uri); + + if ($this->is_album() && $this->id > 1) { + return aws_s3::generate_url("rs/" . $this->relative_path() . "/.album.jpg", ($this->view_1 == 1 ? false : true), $this->updated); + } + else { + return aws_s3::generate_url("rs/" . $this->relative_path(), ($this->view_1 == 1 ? false : true), $this->updated); + } + } + +} \ No newline at end of file diff --git a/3.0/modules/aws_s3/module.info b/3.0/modules/aws_s3/module.info new file mode 100644 index 00000000..6c301507 --- /dev/null +++ b/3.0/modules/aws_s3/module.info @@ -0,0 +1,3 @@ +name = "Amazon S3" +description = "Seamlessly transfer your Gallery data to Amazon S3 CDN for a lightning fast gallery" +version = 1 diff --git a/3.0/modules/aws_s3/views/admin_aws_s3.html.php b/3.0/modules/aws_s3/views/admin_aws_s3.html.php new file mode 100644 index 00000000..961186e3 --- /dev/null +++ b/3.0/modules/aws_s3/views/admin_aws_s3.html.php @@ -0,0 +1,13 @@ + + +
+

+ +

+ +
+ +
+
+ + From 565e616909d4cadcaf17512ff19ef75c20203d1f Mon Sep 17 00:00:00 2001 From: danneh3826 Date: Sun, 28 Nov 2010 19:59:40 +0800 Subject: [PATCH 06/14] added aws_s3 module --- .../aws_s3/helpers/MY_embedlinks_block.php | 20 ++++++++++++++++++- .../aws_s3/helpers/MY_embedlinks_theme.php | 20 ++++++++++++++++++- 3.0/modules/aws_s3/helpers/MY_item.php | 20 ++++++++++++++++++- 3.0/modules/aws_s3/helpers/aws_s3.php | 20 ++++++++++++++++++- 3.0/modules/aws_s3/helpers/aws_s3_event.php | 20 ++++++++++++++++++- .../aws_s3/helpers/aws_s3_installer.php | 20 ++++++++++++++++++- 3.0/modules/aws_s3/helpers/aws_s3_task.php | 20 ++++++++++++++++++- 3.0/modules/aws_s3/models/MY_Item_Model.php | 20 ++++++++++++++++++- 8 files changed, 152 insertions(+), 8 deletions(-) diff --git a/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php b/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php index fe251b1c..e9ca1611 100644 --- a/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php +++ b/3.0/modules/aws_s3/helpers/MY_embedlinks_block.php @@ -1,4 +1,22 @@ - Date: Sun, 28 Nov 2010 19:22:01 +0800 Subject: [PATCH 07/14] Comments --- 3.0/modules/user_chroot/helpers/user_chroot_installer.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/3.0/modules/user_chroot/helpers/user_chroot_installer.php b/3.0/modules/user_chroot/helpers/user_chroot_installer.php index ee89fb13..f12a2ed1 100644 --- a/3.0/modules/user_chroot/helpers/user_chroot_installer.php +++ b/3.0/modules/user_chroot/helpers/user_chroot_installer.php @@ -19,6 +19,9 @@ */ class user_chroot_installer { + /** + * Create the table user_chroot when installing the module. + */ static function install() { $db = Database::instance(); $db->query("CREATE TABLE IF NOT EXISTS {user_chroots} ( @@ -31,7 +34,7 @@ class user_chroot_installer { } /** - * Drops the table of user chroot when the module is uninstalled. + * Drops the table user_chroot when uninstalling the module. */ static function uninstall() { $db = Database::instance(); From a23cf114dc98acb0207b983f75a017b160511629 Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 19:18:42 +0800 Subject: [PATCH 08/14] - Code cleanup - Fix some bugs - Serious performances improvement when building the tree (regression: tree is not alphabetically ordered) --- .../user_chroot/helpers/user_chroot_event.php | 122 +++++++++--------- 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/user_chroot_event.php b/3.0/modules/user_chroot/helpers/user_chroot_event.php index c43c6dc4..51027a44 100644 --- a/3.0/modules/user_chroot/helpers/user_chroot_event.php +++ b/3.0/modules/user_chroot/helpers/user_chroot_event.php @@ -21,92 +21,94 @@ class user_chroot_event_Core { /** - * Called just before a user is deleted. This will remove the user from - * the user_chroot directory. + * Called just before a user deletion. */ - static function user_before_delete($user) { - ORM::factory("user_chroot")->delete($user->id); + public static function user_before_delete($user) { + ORM::factory('user_chroot', $user->id)->delete(); } /** - * Called when admin is adding a user + * Called just before an item deletion. + */ + public static function item_before_delete($item) { + if( $item->is_album() ) { + ORM::factory('user_chroot')->where('album_id', '=', $item->id)->delete(); + } + } + + /** + * Called when building the 'Add user' form for an admin. */ static function user_add_form_admin($user, $form) { - $form->add_user->dropdown("user_chroot") + $form->add_user->dropdown('user_chroot') ->label(t("Root Album")) - ->options(self::createGalleryArray()) - ->selected(0); + ->options(self::albumsTreeArray()) + ->selected(1); } /** - * Called after a user has been added + * Called just after a user has been added by an admin. */ - static function user_add_form_admin_completed($user, $form) { - $user_chroot = ORM::factory("user_chroot")->where("id", "=", $user->id)->find(); - $user_chroot->id = $user->id; - $user_chroot->album_id = $form->add_user->user_chroot->value; - $user_chroot->save(); - } - - /** - * Called when admin is editing a user - */ - static function user_edit_form_admin($user, $form) { - $user_chroot = ORM::factory("user_chroot")->where("id", "=", $user->id)->find(); - if ($user_chroot->loaded()) { - $selected = $user_chroot->album_id; - } else { - $selected = 0; + public static function user_add_form_admin_completed($user, $form) { + if( $form->add_user->user_chroot->value > 1 ) { + $user_chroot = ORM::factory('user_chroot'); + $user_chroot->id = $user->id; + $user_chroot->album_id = $form->add_user->user_chroot->value; + $user_chroot->save(); } - $form->edit_user->dropdown("user_chroot") + } + + /** + * Called when building the 'Edit user' form for an admin. + */ + public static function user_edit_form_admin($user, $form) { + $user_chroot = ORM::factory('user_chroot', $user->id); + + $selected = ( $user_chroot->loaded() ) + ? $user_chroot->album_id + : 1; + + $form->edit_user->dropdown('user_chroot') ->label(t("Root Album")) - ->options(self::createGalleryArray()) + ->options(self::albumsTreeArray()) ->selected($selected); } /** - * Called after a user had been edited by the admin + * Called just after a user has been edited by an admin. */ - static function user_edit_form_admin_completed($user, $form) { - $user_chroot = ORM::factory("user_chroot")->where("id", "=", $user->id)->find(); - if ($user_chroot->loaded()) { - $user_chroot->album_id = $form->edit_user->user_chroot->value; + public static function user_edit_form_admin_completed($user, $form) { + if( $form->edit_user->user_chroot->value <= 1 ) { + ORM::factory('user_chroot')->delete($user->id); + } else { - $user_chroot->id = $user->id; + $user_chroot = ORM::factory('user_chroot', $user->id); + + if( !$user_chroot->loaded() ) { + $user_chroot = ORM::factory('user_chroot'); + $user_chroot->id = $user->id; + } + $user_chroot->album_id = $form->edit_user->user_chroot->value; + $user_chroot->save(); } - $user_chroot->save(); - } - - - /** - * Creates an array of galleries - */ - static function createGalleryArray() { - $array[0] = "none"; - $root = ORM::factory("item", 1); - self::tree($root, "", $array); - return $array; } /** - * recursive function to build array for drop down list + * Generate an array representing the hierarchy of albums. */ - static function tree($parent, $dashes, &$array) { - if ($parent->id == "1") { - $array[$parent->id] = ORM::factory("item", 1)->title; - } else { - $array[$parent->id] = "$dashes $parent->name"; - } - - $albums = ORM::factory("item") - ->where("parent_id", "=", $parent->id) - ->where("type", "=", "album") - ->order_by("title", "ASC") + private static function albumsTreeArray($level_marker = '    ') { + $tree = array(); + $albums = ORM::factory('item') + ->where('type', '=', 'album') + ->order_by('left_ptr', 'ASC') ->find_all(); - foreach ($albums as $album) { - self::tree($album, "-$dashes", $array); + + foreach($albums as $album) { + $tree[$album->id] = html::clean( + str_repeat($level_marker, $album->level - 1).' '.$album->title ); } - return; + + return $tree; } } From a4ba3012e7735c5e29051784c66ad3b616ff228a Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 23:03:57 +0800 Subject: [PATCH 09/14] - Overload a forgotten method - Code cleanup --- 3.0/modules/user_chroot/helpers/MY_item.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/MY_item.php b/3.0/modules/user_chroot/helpers/MY_item.php index 423b8ddc..d6368f18 100644 --- a/3.0/modules/user_chroot/helpers/MY_item.php +++ b/3.0/modules/user_chroot/helpers/MY_item.php @@ -24,11 +24,17 @@ class item extends item_Core { if( user_chroot::album() ) { $model->and_open() - ->and_where("items.left_ptr", ">=", user_chroot::album()->left_ptr) - ->and_where("items.right_ptr", "<=", user_chroot::album()->right_ptr) + ->and_where('items.left_ptr', '>=', user_chroot::album()->left_ptr) + ->and_where('items.right_ptr', '<=', user_chroot::album()->right_ptr) ->close(); } return $model; } + + static function root() { + return ( user_chroot::album() ) + ? user_chroot::album() + : parent::root(); + } } From 52bef7234d0e6f7557f495e3f03137d295d49965 Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 19:23:35 +0800 Subject: [PATCH 10/14] Explicit is better than implicit... --- 3.0/modules/user_chroot/helpers/user_chroot_installer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/user_chroot_installer.php b/3.0/modules/user_chroot/helpers/user_chroot_installer.php index f12a2ed1..f5f597c4 100644 --- a/3.0/modules/user_chroot/helpers/user_chroot_installer.php +++ b/3.0/modules/user_chroot/helpers/user_chroot_installer.php @@ -22,7 +22,7 @@ class user_chroot_installer { /** * Create the table user_chroot when installing the module. */ - static function install() { + public static function install() { $db = Database::instance(); $db->query("CREATE TABLE IF NOT EXISTS {user_chroots} ( `id` int(9) NOT NULL, @@ -36,7 +36,7 @@ class user_chroot_installer { /** * Drops the table user_chroot when uninstalling the module. */ - static function uninstall() { + public static function uninstall() { $db = Database::instance(); $db->query("DROP TABLE IF EXISTS {user_chroots};"); } From 071f3f383167eed6d8a68219c19484b8c895b0ce Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 19:37:57 +0800 Subject: [PATCH 11/14] - Code cleanup - Comments --- 3.0/modules/user_chroot/helpers/user_chroot.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/user_chroot.php b/3.0/modules/user_chroot/helpers/user_chroot.php index 4d0a8499..8afa753f 100644 --- a/3.0/modules/user_chroot/helpers/user_chroot.php +++ b/3.0/modules/user_chroot/helpers/user_chroot.php @@ -21,18 +21,21 @@ class user_chroot_Core { private static $_album = null; + /** + * Return the root album of the current user, or false if the user is not + * chrooted. + */ public static function album() { if( is_null(self::$_album) ) { self::$_album = false; - $user = identity::active_user(); + $item = ORM::factory('item') + ->join('user_chroots', 'items.id', 'user_chroots.album_id') + ->where('user_chroots.id', '=', identity::active_user()->id) + ->find(); - $user_chroot = ORM::factory("user_chroot", $user->id); - if( $user_chroot->loaded() && $user_chroot->album_id != 0 ) { - $item = ORM::factory("item", $user_chroot->album_id); - if( $item->loaded() ) { - self::$_album = $item; - } + if( $item->loaded() ) { + self::$_album = $item; } } From e04c384fbd0955069805a1f3bd1c423b64aca22f Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 23:04:59 +0800 Subject: [PATCH 12/14] - Bugfix - Comments - Code cleanup --- 3.0/modules/user_chroot/helpers/MY_url.php | 29 ++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/MY_url.php b/3.0/modules/user_chroot/helpers/MY_url.php index 31818fa4..b693c9c5 100644 --- a/3.0/modules/user_chroot/helpers/MY_url.php +++ b/3.0/modules/user_chroot/helpers/MY_url.php @@ -18,25 +18,40 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -// /!\ Hack: There is no good way to extend gallery url +/* + * /!\ Hack + * Module 'gallery' already provides a class 'url' which extends url_Core. This + * hack renames class 'url', so user_chroot can have its own (which extends the + * original) + */ $gallery_url = file_get_contents(MODPATH . 'gallery/helpers/MY_url.php'); -$gallery_url = preg_replace('#^<\?php #', '', $gallery_url); -$gallery_url = preg_replace('#class url extends url_Core#', 'class url_G3 extends url_Core', $gallery_url); +$gallery_url = str_replace('relative_url().'/'.Router::$current_uri, '/'); - Router::$current_uri = trim(user_chroot::album()->relative_url().'/'.Router::$current_uri, '/'); + } else if( is_null(Router::$controller) && Router::$current_uri != '' ) { + // Non-root album requested + Router::$current_uri = trim(user_chroot::album()->relative_url().'/'.Router::$current_uri, '/'); + } } return parent::parse_url(); } + /** + * Remove the chroot part of the URI. + */ static function site($uri = '', $protocol = FALSE) { if( user_chroot::album() ) { $uri = preg_replace('#^'.user_chroot::album()->relative_url().'#', '', $uri); From fcf8ecca1d749698e369f381b610370c94ef81b1 Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 23:09:18 +0800 Subject: [PATCH 13/14] Comments --- 3.0/modules/user_chroot/helpers/MY_access.php | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/MY_access.php b/3.0/modules/user_chroot/helpers/MY_access.php index 5f73b4ca..e1a854f8 100644 --- a/3.0/modules/user_chroot/helpers/MY_access.php +++ b/3.0/modules/user_chroot/helpers/MY_access.php @@ -21,22 +21,27 @@ class access extends access_Core { /** - * Does the active user have this permission on this item? - * - * @param string $perm_name - * @param Item_Model $item - * @return boolean + * If the user is chrooted, deny access outside of the chroot. + */ + static function user_can($user, $perm_name, $item) { + if( $user->id == identity::active_user()->id && user_chroot::album() ) { + if( $item->left_ptr < user_chroot::album()->left_ptr || user_chroot::album()->right_ptr < $item->right_ptr ) { + return false; + } + } + + return parent::user_can($user, $perm_name, $item); + } + + /** + * Copied from modules/gallery/helpers/access.php because of the usage of self:: */ static function can($perm_name, $item) { return self::user_can(identity::active_user(), $perm_name, $item); } /** - * If the active user does not have this permission, failed with an access::forbidden(). - * - * @param string $perm_name - * @param Item_Model $item - * @return boolean + * Copied from modules/gallery/helpers/access.php because of the usage of self:: */ static function required($perm_name, $item) { if (!self::can($perm_name, $item)) { @@ -48,14 +53,4 @@ class access extends access_Core { } } } - - static function user_can($user, $perm_name, $item) { - if( $user->id == identity::active_user()->id && user_chroot::album() ) { - if( $item->left_ptr < user_chroot::album()->left_ptr || user_chroot::album()->right_ptr < $item->right_ptr ) { - return false; - } - } - - return parent::user_can($user, $perm_name, $item); - } } From 01158eb356dff72445c73dbeae36b3953db860a1 Mon Sep 17 00:00:00 2001 From: Romain LE DISEZ Date: Sun, 28 Nov 2010 23:15:56 +0800 Subject: [PATCH 14/14] Comments and code cleanup Overload ORM_MPTT::parent() --- .../helpers/user_chroot_installer.php | 8 ++++---- .../user_chroot/libraries/MY_ORM_MPTT.php | 20 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/3.0/modules/user_chroot/helpers/user_chroot_installer.php b/3.0/modules/user_chroot/helpers/user_chroot_installer.php index f5f597c4..dd92fdf0 100644 --- a/3.0/modules/user_chroot/helpers/user_chroot_installer.php +++ b/3.0/modules/user_chroot/helpers/user_chroot_installer.php @@ -24,13 +24,13 @@ class user_chroot_installer { */ public static function install() { $db = Database::instance(); - $db->query("CREATE TABLE IF NOT EXISTS {user_chroots} ( + $db->query('CREATE TABLE IF NOT EXISTS {user_chroots} ( `id` int(9) NOT NULL, `album_id` int(9) default NULL, PRIMARY KEY (`id`), UNIQUE KEY(`id`)) - DEFAULT CHARSET=utf8;"); - module::set_version("user_chroot", 1); + DEFAULT CHARSET=utf8;'); + module::set_version('user_chroot', 1); } /** @@ -38,6 +38,6 @@ class user_chroot_installer { */ public static function uninstall() { $db = Database::instance(); - $db->query("DROP TABLE IF EXISTS {user_chroots};"); + $db->query('DROP TABLE IF EXISTS {user_chroots};'); } } diff --git a/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php b/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php index e31124ca..767d2645 100644 --- a/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php +++ b/3.0/modules/user_chroot/libraries/MY_ORM_MPTT.php @@ -19,8 +19,10 @@ */ class ORM_MPTT extends ORM_MPTT_Core { + /** + * Copied from modules/gallery/libraries/ORM_MPTT.php, not sure of the reason... + */ private $model_name = null; - function __construct($id=null) { parent::__construct($id); $this->model_name = inflector::singular($this->table_name); @@ -31,13 +33,13 @@ class ORM_MPTT extends ORM_MPTT_Core { * * @return ORM */ - /*function parent() { + function parent() { if( user_chroot::album() && user_chroot::album()->id == $this->id ) { return null; } else { return parent::parent(); } - }*/ + } /** * Return all the parents of this node, in order from root to this node's immediate parent. @@ -46,14 +48,14 @@ class ORM_MPTT extends ORM_MPTT_Core { */ function parents() { $select = $this - ->where("left_ptr", "<=", $this->left_ptr) - ->where("right_ptr", ">=", $this->right_ptr) - ->where("id", "<>", $this->id) - ->order_by("left_ptr", "ASC"); + ->where('left_ptr', '<=', $this->left_ptr) + ->where('right_ptr', '>=', $this->right_ptr) + ->where('id', '<>', $this->id) + ->order_by('left_ptr', 'ASC'); if( user_chroot::album() ) { - $select->where("left_ptr", ">=", user_chroot::album()->left_ptr); - $select->where("right_ptr", "<=", user_chroot::album()->right_ptr); + $select->where('left_ptr', '>=', user_chroot::album()->left_ptr); + $select->where('right_ptr', '<=', user_chroot::album()->right_ptr); } return $select->find_all();