CarrierWave, Paperclip, এবং Dragonfly-এর মতো অনেক ফাইল আপলোডিং জেম আছে। उन جميعًا তাদের নিজস্ব специফিসিটি রেখে আছে, এবং সম্ভবত আপনি ইতিমধ্যে এই জেমগুলোর কমপক্ষে একটি ব্যবহার করেছেন।
আজ, যাইহোক, আমি একটি আপেক্ষিক নতুন, কিন্তু খুব কুল সমাধান পরিচয় করাতে চাই যার নাম Shrine, তৈরি করেছেন Janko Marohnić। কিছু অন্য সাদৃশ্য জেমের বিপরীতে, এর একটি মডুলার পদ্ধতি রয়েছে, অর্থাৎ প্রতিটি ফিচার একটি মডিউল (অথবা Shrine-এর পরিভাষায় প্লাগইন) হিসেবে প্যাক করা থাকে। ভ্যালিডেশন সাপোর্ট চান? একটি প্লাগইন যোগ করুন। ফাইল প্রসেসিং করতে চান? একটি প্লাগইন যোগ করুন! আমি এই পদ্ধতিটি খুব পছন্দ করি কারণ এটি আপনাকে সহজে কন্ট্রোল করতে দেয় কোন ফিচারগুলো কোন মডেলের জন্য উপলব্ধ থাকবে।
এই নিবন্ধে আমি আপনাকে দেখাব কীভাবে:
- রেইলস অ্যাপ্লিকেশনে Shrine ইন্টিগ্রেট করতে হয়
- এটি কনফিগার করতে হয় (গ্লোবালি এবং প্রতি-আপলোডার)
- ফাইল আপলোড করার ক্ষমতা যোগ করতে হয়
- ফাইল প্রসেস করতে হয়
- ভ্যালিডেশন নিয়ম যোগ করতে হয়
- অতিরিক্ত মেটাডেটা সংরক্ষণ করতে হয় এবং Amazon S3-এ ক্লাউড ফাইল স্টোরেজ ব্যবহার করতে হয়
এই নিবন্ধের সোর্স কোড গিটহাবে পাওয়া যাবে।
ওয়ার্কিং ডেমো এখানে পাওয়া যাবে।
Shrine ইন্টিগ্রেট করা
শুরু করতে, ডিফল্ট টেস্টিং স্যুট ছাড়া একটি নতুন রেইলস অ্যাপ্লিকেশন তৈরি করুন:
rails new FileGuru -T
এই ডেমোতে আমি রেইলস ৫ ব্যবহার করছি, কিন্তু অধিকাংশ কনসেপ্ট ভার্সন ৩ এবং ৪-এও প্রযোজ্য।
আপনার Gemfile-এ Shrine জেমটি যোগ করুন:
gem "shrine"
তারপর চালান:
bundle install
এখন আমাদের একটি মডেল লাগবে যা আমি Photo বলব। Shrine সকল ফাইল-সম্পর্কিত ইনফরমেশন একটি স্পেশাল টেক্সট কলামে সংরক্ষণ করে যার সাফিক্স থাকে _data। সংশ্লিষ্ট মাইগ্রেশন তৈরি ও প্রয়োগ করুন:
rails g model Photo title:string image_data:text rails db:migrate
নোট করুন যে রেইলসের পুরনো ভার্সনগুলোর জন্য Последняя কমান্ডটি হবে:
rake db:migrate
Shrine-এর কনফিগারেশন অপশনগুলো গ্লোবালি এবং প্রতি-মডেল দুভাবেই সেট করা যায়। গ্লোবাল সেটিংস হয় ইনিশিয়ালাইজার ফাইলের ভিতরে। সেখানে আমি প্রয়োজনীয় ফাইল এবং প্লাগইন হুক আপ করব। প্লাগইনগুলো Shrine-এ ফাংশনালিটির টুকরোকে আলাদা মডিউলে এক্সট্রাক্ট করতে ব্যবহৃত হয়, যাতে আপনাকে সকল উপলব্ধ ফিচারের পূর্ণ کنترل থাকে। উদাহরণস্বরূপ, ভ্যালিডেশন, ইমেজ প্রসেসিং, ক্যাশিং আট্যাচমেন্টস ইত্যাদির জন্য প্লাগইন রয়েছে।
এখন 듸টি প্লাগইন যোগ করি: একটি ActiveRecord সাপোর্ট করার জন্য এবং অন্যটি লগিং সেট আপ করার জন্য। এগুলো গ্লোবালি ইনক্লুড হবে। 더불어 ফাইল সিস্টেম স্টোরেজ সেট আপ করি:
config/initializers/shrine.rb
require "shrine"
require "shrine/storage/file_system"
Shrine.plugin :activerecord
Shrine.plugin :logging, logger: Rails.logger
Shrine.storages = {
cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
}
লগার কনসোলে কিছু ডিবাগ ইনফরমেশন আউটপুট করবে যা বলবে ফাইল প্রসেস করতে কতক্ষণ লাগল। এটি কাজে আসতে পারে।
2015-10-09T20:06:06.676Z #25602: STORE[cache] ImageUploader[:avatar] User[29543] 1 file (0.1s) 2015-10-09T20:06:06.854Z #25602: PROCESS[store]: ImageUploader[:avatar] User[29543] 1-3 files (0.22s) 2015-10-09T20:06:07.133Z #25602: DELETE[destroyed]: ImageUploader[:avatar] User[29543] 3 files (0.07s)
সকল আপলোড হওয়া ফাইল public/uploads ডিরেক্টরিতে সংরক্ষিত হবে। আমি এই ফাইলগুলো Git-এ ট্র্যাক করব না, তাই এই ফোল্ডারকে বাদ দিন:
.gitignore
public/uploads
এখন একটি স্পেশাল "আপলোডার" ক্লাস তৈরি করুন যা মডেল-স্পেসিফিক সেটিংস হোস্ট করবে। ফিলহাল এই ক্লাসটি খালি থাকবে:
models/image_uploader.rb
class ImageUploader < Shrine end
শেষে, এই ক্লাসটিকে Photo মডেলে অন্তর্ভুক্ত করুন:
models/photo.rb
include ImageUploader[:image]
[:image] একটি ভার্চুয়াল অ্যাট্রিবিউট যোগ করে যা ফর্ম কনস্ট্রাক্ট করার সময় ব্যবহার হবে। উপরের লাইনটি এভাবে রিরাইট করা যায়:
include ImageUploader.attachment(:image) # অথবা include ImageUploader::Attachment.new(:image)
বढ़িয়া! এখন মডেলটি Shrine-এর ফাংশনালিটি দিয়ে সজ্জিত, এবং আমরা পরবর্তী ধাপে যেতে পারি।
কন্ট্রোলার, ভিউ, এবং রাউটস
এই ডেমোর জন্য, আমাদের ফটো ম্যানেজ করার জন্য শুধু একটি কন্ট্রোলার লাগবে। index পেজটি রুট হিসেবে কাজ করবে:
pages_controller.rb
class PhotosController < ApplicationController
def index
@photos = Photo.all
end
end
ভিউ:
views/photos/index.html.erb
<h1>Photos</h1> <%= link_to 'Add Photo', new_photo_path %> <%= render @photos %>
@photos অ্যারেটি রেন্ডার করার জন্য একটি পার্শিয়াল লাগবে:
views/photos/_photo.html.erb
<div>
<% if photo.image_data? %>
<%= image_tag photo.image_url %>
<% end %>
<p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>
image_data? ActiveRecord-এর দেওয়া একটি মেথড যা চেক করে একটি রেকর্ডে ইমেজ আছে কিনা।
image_url Shrine-এর একটি মেথড যা সিম্পলি মূল ছবির পাথ রিটার্ন করে। অবশ্য, একটি ছোট থাম্বনেইল দেখানো আরও ভালো, কিন্তু সেটি আমরা পরে দেখব।
সকল প্রয়োজনীয় রাউট যোগ করুন:
config/routes.rb
resources :photos, only: [:new, :create, :index, :edit, :update] root 'photos#index'
এটুকুই—বুনিয়াদি কাজ হয়ে গেল, এবং আমরা আসল অংশে যেতে পারি!
ফাইল আপলোড করা
এই সেকশনে আমি দেখাব কীভাবে আসলে ফাইল আপলোড করার ফাংশনালিটি যোগ করতে হয়। কন্ট্রোলার অ্যাকশনগুলো খুব সিম্পল:
photos_controller.rb
def new
@photo = Photo.new
end
def create
@photo = Photo.new(photo_params)
if @photo.save
flash[:success] = 'Photo added!'
redirect_to photos_path
else
render 'new'
end
end
একমাত্র গোটাকা হলো স্ট্রং প্যারামিটার্সের জন্য আপনাকে image ভার্চুয়াল অ্যাট্রিবিউট অনুমতি দিতে হবে, image_data নয়।
photos_controller.rb
private def photo_params params.require(:photo).permit(:title, :image) end
new ভিউ তৈরি করুন:
views/photos/new.html.erb
<h1>Add photo</h1> <%= render 'form' %>
ফর্মের পার্শিয়ালটিও ত্রিবিধ:
views/photos/_form.html.erb
<%= form_for @photo do |f| %> <%= render "shared/errors", object: @photo %> <%= f.label :title %> <%= f.text_field :title %> <%= f.label :image %> <%= f.file_field :image %> <%= f.submit %> <% end %>
আবার লক্ষ্য করুন যে আমরা image অ্যাট্রিবিউট ব্যবহার করছি, image_data নয়।
শেষে, এরর দেখানোর জন্য আরেকটি পার্শিয়াল যোগ করুন:
views/shared/_errors.html.erb
<% if object.errors.any? %>
<h3>The following errors were found:</h3>
<ul>
<% object.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
এটুকুই—এখন আপনি ছবি আপলোড করা শুরু করতে পারেন।
ভ্যালিডেশন
অবশ্যই, ডেমো অ্যাপ্লিকেশন সম্পূর্ণ করার জন্য আরও অনেক কাজ বাকি। মূল সমস্যা হলো ইউজাররা যেকোনো টাইপের ফাইল যেকোনো সাইজে আপলোড করতে পারে, যা খুব ভালো না। তাই ভ্যালিডেশন সাপোর্টের জন্য আরেকটি প্লাগইন যোগ করুন:
config/initializers/shrine.rb
Shrine.plugin :validation_helpers
ImageUploader-এর জন্য ভ্যালিডেশন লজিক সেট আপ করুন:
models/image_uploader.rb
Attacher.validate do validate_max_size 1.megabyte, message: "is too large (max is 1 MB)" validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png'] end
আমি শুধু ১MB-এর নিচে JPG এবং PNG ইমেজ আপলোড করার অনুমতি দিচ্ছি। আপনার ইচ্ছামতো এই নিয়মগুলো টিউন করুন।
MIME টাইপস
একটি আরো গুরুত্বপূর্ণ বিষয় নোট করুন যে, ডিফল্টরূপে Shrine একটি ফাইলের MIME টাইপ Content-Type HTTP হেডার ব্যবহার করে নির্ধারণ করে। এই হেডার ব্রাউজার পাস করে এবং শুধু ফাইলের এক্সটেনশন বেস করে সেট করা হয়, যা সবসময় ইচ্ছামতো নয়।
আপনি যদি ফাইলের কন্টেন্ট বেস করে MIME টাইপ নির্ধারণ করতে চান, তবে determine_mime_type নামক একটি প্লাগইন ব্যবহার করুন। আমি এটিকে আপলোডার ক্লাসের ভিতরে ইনক্লুড করব, কারণ অন্যান্য মডেলগুলো এই ফাংশনালিটি প্রয়োজন নাও করতে পারে:
models/image_uploader.rb
plugin :determine_mime_type
এই প্লাগইন ডিফল্টরূপে Linux-এর file ইউটিলিটি ব্যবহার করবে।
আট্যাচড ইমেজ ক্যাশ করা
বর্তমানে, যখন একটি ইউজার ভুল ডেটা দিয়ে ফর্ম সাবমিট করে, ফর্মটি এররসহ আবার দেখানো হবে। সমস্যাটি হলো আট্যাচ করা ছবিটি হারিয়ে যাবে, এবং ইউজারকে আবার সেটি সিলেক্ট করতে হবে। এটি খুব সহজে ঠিক করা যায় cached_attachment_data নামক আরেকটি প্লাগইন দিয়ে:
models/image_uploader.rb
plugin :cached_attachment_data
এখন আপনার ফর্মে একটি হিডেন ফিল্ড যোগ করুন।
views/photos/_form.html.erb
<%= f.hidden_field :image, value: @photo.cached_image_data %> <%= f.label :image %> <%= f.file_field :image %>
একটি ফটো এডিট করা
এখন ছবি আপলোড করা যাচ্ছে, কিন্তু এডিট করার কোনো উপায় নেই, তাই তাড়াতাড়ি এটিকেও ঠিক করি। সংশ্লিষ্ট কন্ট্রোলারের অ্যাকশনগুলো কিছুটা ত্রিবিধ:
photos_controller.rb
def edit
@photo = Photo.find(params[:id])
end
def update
@photo = Photo.find(params[:id])
if @photo.update_attributes(photo_params)
flash[:success] = 'Photo edited!'
redirect_to photos_path
else
render 'edit'
end
end
একই _form পার্শিয়ালটি ব্যবহার করা হবে:
views/photos/edit.html.erb
<h1>Edit Photo</h1> <%= render 'form' %>
ভালো, কিন্তু পর্যাপ্ত নয়: ইউজাররা এখনও একটি আপলোড করা ছবি মুছে ফেলতে পারেন না। এর অনুমতি দিতে আমাদের— অনুমান করুন—আরেকটি প্লাগইন লাগবে:
models/image_uploader.rb
plugin :remove_attachment
এটি :remove_image নামে একটি ভার্চুয়াল অ্যাট্রিবিউট ব্যবহার করে, তাই কন্ট্রোলারে এটিকে অনুমতি দিন:
photos_controller.rb
def photo_params params.require(:photo).permit(:title, :image, :remove_image) end
এখন যদি রেকর্ডে আট্যাচমেন্ট থাকে তবে একটি চেকবক্স দেখান ছবি মুছে ফেলার জন্য:
views/photos/_form.html.erb
<% if @photo.image_data? %> Remove attachment: <%= f.check_box :remove_image %> <% end %>
থাম্বনেইল ইমেজ জেনারেট করা
বর্তমানে আমরা মূল ছবিগুলো দেখাচ্ছি, যা প্রিভিউর জন্য সেরা পদ্ধতি নয়: ফটোগুলো বড় হতে পারে এবং খুব জায়গা নিতে পারে। অবশ্য, আপনি সিম্পলি CSS width এবং height অ্যাট্রিবিউট ব্যবহার করতে পারেন, কিন্তু সেটিও খারাপ আইডিয়া। আপনি দেখেন, যদি স্টাইল দিয়ে ছবিটি ছোট করা হয়, তবুও ইউজারকে মূল ফাইলটি ডাউনলোড করতে হবে, যা খুব বড় হতে পারে।
তাই সার্ভার সাইডে আপলোডের সময় একটি ছোট প্রিভিউ ইমেজ জেনারেট করা অনেক ভালো। এতে দুটি প্লাগইন এবং দুটি অতিরিক্ত জেম লাগবে। প্রথমে জেমগুলো যোগ করুন:
gem "image_processing" gem "mini_magick", ">= 4.3.5"
Image_processing Shrine-এর লেখক তৈরি করা একটি স্পেশাল জেম। এটি ইমেজ ম্যানিপুলেট করার জন্য কিছু হাই-লেভেল হেল্পার মেথড প্রদান করে। এই জেমটি filaয় mini_magick-র ওপর নির্ভর করে, যা ImageMagick-এর একটি রুবি র্যাপার। আপনি অনুমান করলেন, এই ডেমো চালানোর জন্য আপনার সিস্টেমে ImageMagick থাকতে হবে।
এই নতুন জেমগুলো ইনস্টল করুন:
bundle install
এখন প্লাগইনগুলো তাদের ডিপেন্ডেন্সিসহ ইনক্লুড করুন:
models/image_uploader.rb
require "image_processing/mini_magick" class ImageUploader < Shrine include ImageProcessing::MiniMagick plugin :processing plugin :versions # other code... end
Processing হলো वो প্লাগইন যা আমাদের ইমেজ ম্যানিপুলেট করতে দেয় (উদাহরণস্বরূপ, স্মল করা, রোটেট করা, অন্য ফরম্যাটে কনভার্ট করা, ইত্যাদি)। Versions, সেইপাশে, আমাদের ইমেজের বিভিন্ন ভ্যারিয়েন্ট রাখতে দেয়। এই ডেমোর জন্য, দুটি ভার্সন সংরক্ষিত হবে: "original" এবং "thumb" (রিসাইজ করে 300x300-এ)।
ইমেজ প্রসেস করে এর দুটি ভার্সন সংরক্ষণ করার কোড:
models/image_uploader.rb
class ImageUploader < Shrine
process(:store) do |io, context|
{
original: io,
thumb: resize_to_limit!(io.download, 300, 300)
}
end
end
resize_to_limit! image_processing জেম দ্বারা প্রদত্ত একটি মেথড। এটি সহজে একটি ইমেজকে 300x300-এ স্মল করবে যদি সেটি বড় হয়, এবং কিছুই করবে না যদি সেটি ছোট হয়। കൂടে এটি মূল এ্যাসপেক্ট রেশিও রক্ষা করে।
এখন ছবি দেখানোর সময়, আপনার শুধু :original বা :thumb আর্গুমেন্ট দিতে হবে image_url মেথডে:
views/photos/_photo.html.erb
<div>
<% if photo.image_data? %>
<%= image_tag photo.image_url(:thumb) %>
<% end %>
<p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>
ফর্মের ভিতরেও একইভাবে করা যায়:
views/photos/_form.html.erb
<% if @photo.image_data? %> <%= image_tag @photo.image_url(:thumb) %> Remove attachment: <%= f.check_box :remove_image %> <% end %>
আপলোড কমপ্লিট হওয়ার পর প্রসেস করা ফাইলগুলো অটোমেটিক্যালি ডিলিট করতে চাইলে delete_raw নামক একটি প্লাগইন যোগ করতে পারেন:
models/image_uploader.rb
plugin :delete_raw
ইমেজের মেটাডেটা
আসলে ইমেজ রেন্ডার করার পাশাপাশি, আপনি এর মেটাডেটাও ফেচ করতে পারেন। চলুন উদাহরণস্বরূপ, মূল ফটোর সাইজ এবং MIME টাইপ দেখান:
views/photos/_photo.html.erb
<div>
<% if photo.image_data? %>
<%= image_tag photo.image_url(:thumb) %>
<p>
Size <%= photo.image[:original].size %> bytes<br>
MIME type <%= photo.image[:original].mime_type %><br>
</p>
<% end %>
<p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>
তার ডাইমেনশনস কী? দুর্ভাগ্যক্রমে, সেগুলো ডিফল্টরূপে সংরক্ষিত হয় না, কিন্তু এটি সম্ভব store_dimensions নামক একটি প্লাগইন দিয়ে।
ইমেজের ডাইমেনশনস
store_dimensions প্লাগইন fastimage জেমের ওপর নির্ভর করে, তাই এখন সেটি হুক আপ করুন:
gem 'fastimage'
ভুলবেন না চালান:
bundle install
এখন শুধু প্লাগইনটি ইনক্লুড করুন:
models/image_uploader.rb
plugin :store_dimensions
এবং width এবং height মেথড ব্যবহার করে ডাইমেনশনস দেখান:
views/photos/_photo.html.erb
<div>
<% if photo.image_data? %>
<%= image_tag photo.image_url(:thumb) %>
<p>
Size <%= photo.image[:original].size %> bytes<br>
MIME type <%= photo.image[:original].mime_type %><br>
Dimensions <%= "#{photo.image[:original].width}x#{photo.image[:original].height}" %>
</p>
<% end %>
<p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>
এছাড়া একটি dimensions মেথডও উপলব্ধ যা একটি অ্যারে রিটার্ন করে 와이ডথ এবং হাইট ধারণ করে (উদাহরণস্বরূপ, [500, 750])।
ক্লাউডে যাওয়া
ডেভেলপাররা সার্ভার আপলোড করা ফাইল হোস্ট করার জন্য প্রায়ই ক্লাউড সার্ভিস বেছে নেন, এবং Shrine সেটি সম্ভব করে দেয়। এই সেকশনে, আমি দেখাব কীভাবে ফাইলগুলো Amazon S3-এ আপলোড করতে হয়।
প্রথম ধাপ হিসেবে, Gemfile-এ আরও দুটি জেম যোগ করুন:
gem "aws-sdk", "~> 2.1" group :development do gem 'dotenv-rails' end
aws-sdk S3-এর SDK-এর সাথে কাজ করার জন্য লাগে, আর dotenv-rails ডেভেলপমেন্টে এনভায়রনমেন্ট ভেরিয়েবল ম্যানেজ করতে ব্যবহার হবে।
bundle install
আগে बढ़তে, আপনাকে S3 API অ্যাক্সেস করার জন্য একটি কি পেয়ার প্রাপ্ত করতে হবে। এজন্য Amazon Web Services Console-এ সাইন ইন (অথবা সাইন আপ) করুন এবং Security Credentials > Users-এ যান। S3-এ ফাইল ম্যানিপুলেট করার পারমিশন সহ একটি ইউজার তৈরি করুন। S3-এ ফুল অ্যাক্সেস দেয় এমন একটি সিম্পল পলিসি:
{
"Version": "2016-11-14",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
তैयার ইউজারের কি পেয়ার ডাউনলোড করুন। বিকল্পভাবে, আপনি রুট এক্সেস কি ব্যবহার করতে পারেন, কিন্তু আমি শদershipে আপনাকে এটিকে ডিসকারেজ করছি কারণ এটি খুবই ইনসিকিউর।
পরবর্তী, আপনার ফাইল হোস্ট করার জন্য একটি S3 বাকেট তৈরি করুন এবং প্রজেক্টের রুটে একটি ফাইল যোগ করুন আপনার কনফিগারেশন হোস্ট করার জন্য:
.env
S3_KEY=YOUR_KEY S3_SECRET=YOUR_SECRET S3_BUCKET=YOUR_BUCKET S3_REGION=YOUR_REGION
কখনোই এই ফাইলকে পাবলিকলে এক্সপোজ করবেন না, এবং নিশ্চিত করুন আপনি এটি Git-এ বাদ দিয়েছেন:
.gitignore
.env
এখন Shrine-এর গ্লোবাল কনফিগারেশন মডিফাই করুন এবং একটি নতুন স্টোরেজ যোগ করুন:
config/initializers/shrine.rb
require "shrine"
require "shrine/storage/s3"
s3_options = {
access_key_id: ENV['S3_KEY'],
secret_access_key: ENV['S3_SECRET'],
region: ENV['S3_REGION'],
bucket: ENV['S3_BUCKET'],
}
Shrine.storages = {
cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
}
এটুকুই! অ্যাপের অন্য কোনো অংশে পরিবর্তন করতে হবে না, এবং আপনি এই নতুন স্টোরেজই আজ testa করতে পারবেন। আপনি যদি S3 থেকে ইন্করেক্ট কি সম্পর্কিত এরর পান, নিশ্চিত করুন যে আপনি সঠিকভাবে কি এবং সিক্রেট কপি করেছেন, কোনো ট্রেইলিং স্পেস বা অনদৃশ্য স্পেশাল সিম্বল ছাড়া।
উপসংহার
এই নিবন্ধের শেষে আসলাম। আশা করি এখন আপনি Shrine ব্যবহার করার বিষয়ে অনেক বেশি আত্মবিশ্বাসী অনুভব করছেন এবং আপনার কোনো একটি প্রজেক্টে এটি ব্যবহার করার উদ্যোগী হয়েছেন। আমরা এই জেমের অনেক ফিচার আলোচনা করেছি, কিন্তু আরও অনেক কিছু রয়েছে, যেমন ফাইলসহ অতিরিক্ত কনটেক্সট সংরক্ষণ করার ক্ষমতা এবং ডায়রেক্ট আপলোড মেকানিজম।
তাই Shrine-এর ডকুমেন্টেশন এবং এর অফিসিয়াল ওয়েবসাইট ব্রাউজ করুন, যা সকল উপলব্ধ প্লাগইন ব্যাপকভাবে বর্ণনা করে। আপনার যদি এই জেম সম্পর্কে অন্য কোনো প্রশ্ন থাকে, সংকোচ না করে পোস্ট করুন। আমার সাথে থাকার জন্য ধন্যবাদ, এবং শীঘ্রই আবার দেখা হবে!