panel->create_new_block("S3 CDN"); $sb->add_text_option(S3Config::ACCESS_KEY_ID, "Access Key ID: "); $sb->add_text_option(S3Config::ACCESS_KEY_SECRET, "
Access Key Secret: "); $sb->add_text_option(S3Config::ENDPOINT, "
Endpoint: "); $sb->add_text_option(S3Config::IMAGE_BUCKET, "
Image Bucket: "); } public function onCommand(CommandEvent $event) { if ($event->cmd == "help") { print "\ts3-sync \n"; print "\t\tsync a post to s3\n\n"; print "\ts3-rm \n"; print "\t\tdelete a leftover file from s3\n\n"; } if ($event->cmd == "s3-sync") { if (preg_match('/^(\d+)-(\d+)$/', $event->args[0], $matches)) { $start = (int)$matches[1]; $end = (int)$matches[2]; } else { $start = (int)$event->args[0]; $end = $start; } foreach(Search::find_images_iterable(tags: ["order=id", "id>=$start", "id<=$end"]) as $image) { print("{$image->id}: {$image->hash}\n"); ob_flush(); $this->sync_post($image); } } if ($event->cmd == "s3-rm") { foreach($event->args as $hash) { print("{$hash}\n"); ob_flush(); $this->remove_file($hash); } } } public function onPageRequest(PageRequestEvent $event) { global $config, $page, $user; if ($event->page_matches("s3/sync")) { if ($user->check_auth_token()) { if ($user->can(Permissions::DELETE_IMAGE) && isset($_POST['image_id'])) { $id = int_escape($_POST['image_id']); if ($id > 0) { $this->sync_post(Image::by_id($id)); log_info("s3", "Manual resync for >>$id", "File re-sync'ed"); $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("post/view/$id")); } } } } } public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { global $user; if ($user->can(Permissions::DELETE_IMAGE)) { $event->add_part(SHM_SIMPLE_FORM( "s3/sync", INPUT(["type" => 'hidden', "name" => 'image_id', "value" => $event->image->id]), INPUT(["type" => 'submit', "value" => 'CDN Re-Sync']), )); } } public function onImageAddition(ImageAdditionEvent $event) { // Tags aren't set at this point, let's wait for the TagSetEvent // $this->sync_post($event->image); } public function onTagSet(TagSetEvent $event) { $this->sync_post($event->image, $event->tags); } public function onImageDeletion(ImageDeletionEvent $event) { $this->remove_file($event->image->hash); } public function onImageReplace(ImageReplaceEvent $event) { $this->remove_file($event->original->hash); $this->sync_post($event->replacement, $event->original->get_tag_array()); } // utils private function get_client() { global $config; $access_key_id = $config->get_string(S3Config::ACCESS_KEY_ID); $access_key_secret = $config->get_string(S3Config::ACCESS_KEY_SECRET); if(is_null($access_key_id) || is_null($access_key_secret)) { return null; } $endpoint = $config->get_string(S3Config::ENDPOINT); $credentials = new \Aws\Credentials\Credentials($access_key_id, $access_key_secret); return new \Aws\S3\S3Client([ 'region' => 'auto', 'endpoint' => $endpoint, 'version' => 'latest', 'credentials' => $credentials, ]); } private function hash_to_path(string $hash) { $ha = substr($hash, 0, 2); $sh = substr($hash, 2, 2); return "$ha/$sh/$hash"; } // underlying s3 interaction functions private function sync_post(Image $image, ?array $new_tags = null) { global $config; // multiple events can trigger a sync, // let's only do one per request if(in_array($image->id, self::$synced)) { return; } self::$synced[] = $image->id; $client = $this->get_client(); if(is_null($client)) { return; } $image_bucket = $config->get_string(S3Config::IMAGE_BUCKET); if(is_null($new_tags)) { $friendly = $image->parse_link_template('$id - $tags.$ext'); } else { $_orig_tags = $image->get_tag_array(); $image->tag_array = $new_tags; $friendly = $image->parse_link_template('$id - $tags.$ext'); $image->tag_array = $_orig_tags; } $client->putObject([ 'Bucket' => $image_bucket, 'Key' => $this->hash_to_path($image->hash), 'Body' => file_get_contents($image->get_image_filename()), 'ACL' => 'public-read', 'ContentType' => $image->get_mime(), 'ContentDisposition' => "inline; filename=\"$friendly\"", ]); } private function remove_file(string $hash) { global $config; $client = $this->get_client(); if(is_null($client)) { return; } $image_bucket = $config->get_string(S3Config::IMAGE_BUCKET); $client->deleteObject([ 'Bucket' => $image_bucket, 'Key' => $this->hash_to_path($hash), ]); } }