What it does
A product made for one person does not fit a normal product page. The customer has to send you a photograph, you have to build the thing, and they have to see it and agree before you spend the material. Done by hand that is a chain of emails, and the chain breaks in the same place every time: nobody can say for certain whether the customer approved the version you actually printed.
MevvAtelier makes that chain part of the order. The order carries the photographs, the preview, the approval and the timestamp on it, and the states it passes through are real WooCommerce order statuses, so they show up in the order list, in reports and in the filters you already use.
- Use it when
- You sell something you produce per order from something the customer sends you, and you want their approval recorded before production starts.
- It is not a product-options plugin
- There is no variation matrix and no per-product option builder. It is one pipeline, with one configurator page, for a product line you make to order.
- The photographs are not media-library attachments
- They are private files with their own retention clock. A customer’s photograph is not a picture of a product and is not treated like one.
Requirements: WordPress 6.0 or newer, PHP 8.1 or newer, and WooCommerce. Without WooCommerce the plugin stops itself and says so — it has no order pipeline of its own to fall back on.
Settings, and the one limit that remains
Everything a shop normally changes lives under Atölye → Ayarlar: what each package is called, what it costs for one to six people, the two add-on prices, which package gets the paint kit free, which one is selected when the page opens, and how long photographs are kept.
You type prices in lira and the plugin stores them as whole minor units. A save is all or nothing: if one field cannot be read, none of them is written and the screen says which one. Half a price table is a table that breaks at the moment a customer is ordering.
- Renaming a package
- The names shipped with the plugin describe 3D-printed figurines because that is the shop it was built for. Type your own — “Small pendant”, “Engraved plaque”, “A4 print” — and that is what the customer sees. The internal key beside each field never changes, so existing orders keep pointing at the right thing.
- Selling fewer than five
- Leave a package’s name empty and it disappears from the order form and is refused by the server if somebody submits it anyway. Its prices stay in the table, so switching it back on later is one word, not a re-entry.
- Which package opens selected
- A commercial decision rather than a technical one: the middle tier anchors the customer upwards, the cheapest anchors them down. If the one you picked is later switched off, the form falls back to the first package that is still on rather than opening with no price at all.
THE LIMIT THAT REMAINS: there are five package slots. You can rename them, price them and switch them off, but you cannot add a sixth. If your line needs more, write to us and say how many — that is the next thing being built, and knowing the real number shapes it.
Installation
- Install and activate WooCommerce first. MevvAtelier refuses to boot without it.
- Install MevvAtelier: upload the zip through Plugins → Add New, or copy the
mevvatelierdirectory into/wp-content/plugins/. - Activate it. On activation the plugin registers its order statuses, creates the photo vault directory and schedules the daily retention job.
- Create a page and put the
[mevvatelier]shortcode on it. That page is your order form. - Place a test order through the page, then open Atölye in the admin menu and walk it through once. Do this before you advertise the page — the round trip takes five minutes and it is the only way to see the emails your store actually sends.
The approval links are signed with a secret generated on first use and stored in your own database. It never leaves your server, and it is not derived from anything we hold.
The three shortcodes
- [mevvatelier]
- The order form. Five steps: package, how many people are in the scene, the photographs, the customer’s details, and a consent box. It shows a running total as the customer chooses, and that total is never what the order is written with — see “Where the prices live”.
- [mevvatelier_tanitim]
- A promotional band for your home page — a headline, a short pitch and a button through to the order form. Takes an attachment id:
[mevvatelier_tanitim gorsel="123"]. - [mevvatelier_gorsel]
- An image from your media library rendered with the responsive sizes WordPress already generated:
[mevvatelier_gorsel id="123" size="large"]. It exists so a promotional page does not print a 2000-pixel photograph into a 380-pixel slot on a phone.
The order page turns page caching off for itself, in the language LiteSpeed, WP Rocket and W3 Total Cache all understand, and sends no-store headers as well. A cached order form submits with a stale token and fails silently, which looks to the customer like a form that does nothing.
Where the prices live
The price of an order is calculated on your server from the customer’s selection alone. A total posted by the browser is ignored outright — asking the browser what to charge is the shortest route to being sold to for nothing. Money is held as whole minor units (kuruş, cents), never as a decimal, so a long order cannot drift by one unit between the invoice and the order.
You edit them under Atölye → Ayarlar, in lira, with a comma for the kuruş. Every package needs a price for every supported number of people, one through six. The whole table is validated before anything is written, and if one field cannot be read the screen tells you which and writes none of them — half a price table is a table that breaks at the moment a customer is ordering.
The whole thing sits in one option, mevvatelier_pricing, if you would rather deploy it with WP-CLI than click. The plugin reads the option back after writing it and refuses to report success unless the read matches, because a shop running behind a persistent object cache can otherwise be told “saved” while it keeps selling at the old price.
wp eval '
$t = MevvAtelier_Settings::pricing_table();
$t["labels"]["orta"] = "Orta boy kolye ucu";
$t["packages"]["orta"][1] = MevvAtelier_Pricing::parse_money( "1.290,50" );
$t["default_package"] = "orta";
var_dump( MevvAtelier_Settings::save_pricing_table( $t ) );
'
The internal package keys are anahtarlik, orta, buyuk, anahtarlik_orta and uclu_set; the add-on keys are paint_kit and rush. These are what the code and your existing orders refer to, so they never change — the name the customer sees is a separate field you can rewrite whenever you like.
The workshop queue
Atölye appears in the admin menu for anyone who can edit shop orders. It lists open orders oldest first and marks the ones past their promised time in red — twenty-four hours normally, six on a rush order. The question it answers is not “what is open” but “what is late”, which is the only one worth putting on a screen.
The operator uploads the preview from the same row. Uploading it stores the file in the vault, records a checksum of exactly what was shown, moves the order to Müşteri onayında and emails the customer — one action, not four things to remember.
- Model bekliyor
wc-mevv-model. The photographs are in, the model is being built. The customer’s link works and tells them exactly this.- Müşteri onayında
wc-mevv-onay. The preview has been sent. The customer can approve it or ask for a correction, as many times as it takes.- Baskıda
wc-mevv-baski. Approved and in production. The revision form is gone from the screen, and the server refuses a revision request at this point too — not just the screen.
Which state may follow which is written as a list of permitted moves rather than forbidden ones. A step nobody thought of is refused by default instead of quietly allowed, which is the difference between a state machine and a suggestion.
What the customer sees
One link, sent by email, that works for sixty days and needs no account. Asking somebody to register before they can look at their own preview is the surest way to lose the approval, so the link carries its own authority: it is signed, it names one order and no other, and a tampered one opens nothing.
The page shows a different screen for each state, and it is never cached — a cached approval page would show one customer’s preview to the next visitor. The approval link appears twice in every email, as a button and as plain text, because some mail clients will not render the button.
- Order received — sent when the order is placed, with the link.
- Preview ready — sent when the operator uploads it. This is the email that asks for a decision.
- Approval recorded — sent when they approve, and it says plainly that production has started.
- Shipped — sent when the operator marks it done.
The emails are drawn through your store’s own WooCommerce template, so they look like the rest of your mail rather than something bolted on.
The photographs
Uploads go to a directory under your uploads folder whose name cannot be guessed, and they are protected four ways at once: an .htaccess rule, a web.config for IIS, an index.php, and file permissions that let nothing but the site read them. Four, because any single one of them is missing or ignored on somebody’s server.
A file is accepted for what its bytes are, not what its name claims. An executable renamed to .jpg is refused at the door — checking the extension is checking the attacker’s own paperwork. Accepted types are JPEG, PNG, WebP and HEIC. The stored name is random; the customer’s original filename never reaches the filesystem, because “ayse-dogum-gunu.jpg” is a child’s name and a date.
Every photograph and every preview is served through a single gateway. Your team is admitted by their WooCommerce order permission; the customer by the signed link, checked against that order. A link built for one order cannot open another order’s file. The files are never given a public URL.
- Retention
- A daily job deletes the uploads and previews of finished orders after a period you set — sixty days out of the box, stored in the
mevvatelier_retention_daysoption. The order, its price and its approval record stay; the photograph does not. - What leaves your server
- Nothing from this plugin. No photograph, no preview, no order, no telemetry and no licence call reaches us from the order pipeline. The vault is a directory on your disk and the log of approvals is your own order meta.
Keeping a child’s photograph on a shop server indefinitely is a liability, not a feature. If your retention promise to customers differs from sixty days, change the option to match what you told them — the number on your page and the number in the plugin should be the same number.
Approval, cancellation and the right of withdrawal
Under the Turkish distance-selling regulation a good prepared to the consumer’s own specification carries no right of withdrawal once it has been made (Mesafeli Sözleşmeler Yönetmeliği, art. 15/1-b). MevvAtelier is built around that: the customer is told so on the approval screen, and the moment they agree is written to the order along with the approval.
Before approval nothing has been produced, so a cancellation costs you a model and no material — cancel the order in WooCommerce and refund it as you would any other. After approval the revision form is gone and the server refuses a revision request, because at that point the material is committed.
This describes what the plugin records, not legal advice. Your own distance-selling contract and pre-information form still have to say the same thing, and MevvLegal is the plugin for that part.
Troubleshooting
- The order form submits and nothing happens
- A cached copy of the page, almost always. The form carries a one-time token and a cached page hands out a stale one, which is refused silently. Purge the page from your cache and confirm the plugin’s no-store headers are reaching the browser; some CDNs strip them at the edge.
- The customer says the link does not work
- Three causes, and the page says which. Past sixty days it has expired and you send a new one. If it was copied out of the email by hand a character is usually missing. If it names another order it was forwarded from a different email.
- The photographs come back as broken images in the admin
- The vault is being served rather than gated — check that the vault directory still holds its
.htaccessandindex.php, and that your host has not reset the permissions. If the files are reachable by URL that is the more urgent half of the problem. - An upload is refused although it is a photograph
- It is being read as something other than JPEG, PNG, WebP or HEIC. Phone screenshots and images exported by design tools are often neither; re-saving as JPEG settles it. The extension is not what is being checked.
- Orders are not moving out of “Model bekliyor”
- Nothing moves them on its own — an operator uploads the preview. If the queue screen is empty for someone on your team, check they have the shop-order editing permission; the screen is gated on that and not on being an administrator.
- Old photographs are still on disk
- The retention job runs on WP-Cron, which only fires when somebody visits the site. On a quiet shop it waits for traffic — or point a real server cron at it. Also check that the orders in question are actually finished; open orders are never purged.
- The prices on the form are not the ones I set
- Your stored table failed validation and the plugin fell back to its defaults rather than sell at a wrong price. There is an admin notice saying so. Every package needs every people-count from one to six, and every amount must be a non-negative whole number in minor units.
When you write to us, send the WordPress, WooCommerce, PHP and plugin versions, the order number, and which of the four emails did or did not arrive. Do not send the customer’s photographs.