$('[name="kullanici_adsoyad[]"]').attr('name', 'kullanici_adsoyad_yeni[]');
Örneğin WordPress, install.php gibi bir dosyaya sahiptir.
Bu dosya adım adım kurulum bilgilerini alır. Veritabanının sunucu adresi, kullanıcı adı, şifre verilerini alır.
Bu verileri aldıktan sonra PHP ile veritabanına bağlanır ve sitede lazım olan tabloları PHP (MySQL'e bağlanarak) oluşturur.
Ayrıca bu veritabanına bağlanma bilgilerini de bir dosyaya yazar. Site veritabanına bağlanmak için bu dosyayı include eder.
Hazır paket satıyor da olsanız, veritabanı kurulumunu siz yapacak da olsanız bu yöntem çok güzel, büyük hız kazandıracak bir yöntem.
Yani,
- MySQL önceden boş halde açılmış olmalı.
- install.php dosyası oluşturun. Kurulum yapmak için bu sayfayı açmalısınız.
- bu sayfa ilk adımda veritabanına bağlantı bilgilerini bir form yardımıyla alsın.
- aldığınız verilerle veritabanına bağlanıp bağlanamadığınızı kontrol edin.
- bağlanamadıysanız hata verin.
- bağlanabildiyseniz bu verileri connection.php adlı bir dosyaya yazdırın. (bu dosyaya sadece siteniz üzerinden erişilmesini sağlamak için güvenlik önlemleri almalısınız). Sonra da
CREATE TABLE
komutlarıyla sitenizde kullanacağınız tüm tabloları PHP ile oluşturun ve gerekiyorsa başlangıç verilerini de bu tablolaraINSERT
edin. - işlemler tamamlandıktan sonra ekrana "işlemler tamam. bu install.php dosyasını silin" diye mesaj verin.
Siteniz, veritabanına bağlantı verilerini almak için connection.php dosyasını include ediyor olmalı. Artık tablolarınız da kurulmuş halde. Böylece siteyi, MySQL'i hiç açmadan kurabilmiş oldunuz.
Bu konuyla ilgili dokümantasyon sayfası:
https://fullcalendar.io/docs/v3/lang
Siz dil dosyasını sayfanıza çağırmamış olabilirsiniz.
lang
Takvim için dil ve yerelleştirme seçeneklerini özelleştirin.
Bu ayarlar v3'te locale olarak yeniden adlandırıldı. Ayrıca, lang.js ve /lang/*.js dosyaları da benzer şekilde yeniden adlandırıldı.
String türünde dil kodu alır. Varsayılan: "en"
Diğer diller nasıl kullanılır?
Kullanmak için dilin JavaScript dosyasını yüklemeniz gerekecek.
Bu dosyalar, lang/ dizinindeki FullCalendar indirmesine dahil edilmiştir.
Ana FullCalendar kitaplığı yüklendikten sonra bir <script />
etiketi aracılığıyla yüklenmeleri gerekir.
<script src='fullcalendar/fullcalendar.js'></script>
<script src='fullcalendar/lang/es.js'></script>
<script>
$(function() {
$('#calendar').fullCalendar({
});
});
</script>
Yalnızca bir dil yüklüyorsanız, dil seçeneğini belirtmeniz gerekmez. FullCalendar, yüklenen en son dil dosyasına bakar ve onu kullanır.
Ancak, birden fazla dil dosyası yüklenmişse veya birleştirilmiş all.js dosyası yüklenmişse, lang seçeneği aracılığıyla hangi dili kullanacağınızı açıkça belirtmelisiniz:
<script src='fullcalendar/fullcalendar.js'></script>
<script src='fullcalendar/lang-all.js'></script>
<script>
$(function() {
$('#calendar').fullCalendar({
lang: 'es'
});
});
</script>
MomentJS ve jQuery UI Datepicker
Bir FullCalendar dil dosyası yüklediğinizde, MomentJS ve jQuery UI Datepicker (kütüphane zaten sayfadaysa) için çevirileri de yükler.
FullCalendar'ın dil dosyasını eklemeden önce Moment ve Datepicker için <script />
etiketlerini eklediğinizden emin olun:
<script src='lib/moment.js'></script>
<script src='lib/jquery-ui.custom-datepicker.js'></script>
<script src='fullcalendar/fullcalendar.js'></script>
<script src='fullcalendar/lang-all.js'></script>
Şöyle bir örnek hazırladım. İnceleyebilirsiniz:
https://codepen.io/ebykdrms/pen/jOxZbjb
JS kısmında en üstteki objede kaç kullanıcının kaç yıldız verdiğini belirleyebiliyorsunuz.
Önemli olan da js kısmı zaten. Yarın bi'gün codepen'den silinirse diye JS kodunun bir örneğini burada paylaşıyorum:
// Hangi yıldızdan kaç tane almış?
const starCounts = {
star1: 4, // 4 kişi 1 puan vermiş
star2: 0, // Kimse 2 puan vermemiş
star3: 3, // 3 kişi 3 puan vermiş
star4: 28, // 28 kişi 4 puan vermiş
star5: 16 // 16 kişi 5 puan vermiş
};
// Puanları bir dizi şeklinde alalım.
// [4, 0, 3, 28, 16]
const allScores = Object.values(starCounts);
// Toplamda kaç kişi puan vermiş?
// (4+0+3+28+16=51)
const totalSelectorCount = allScores.reduce((sum, num)=>sum+num, 0);
// Herkes 5 puan verseydi kaç puan olurdu?
// (51*5=255)
const maxScore = totalSelectorCount * 5;
// Peki şu an herkesin verdipi puana göre kaç puan olmuş?
// (4*1 + 0*2 + 3*3 + 28*4 + 16*5 = 205)
const currentScore = allScores.reduce((sum,num,index)=>sum+(num*(index+1)), 0);
// 255'te 205 ise 100'de kaçtır? Oran-orantı...
// 205/255 = x/100 -> x = 100 * 205 / 255 -> x = 80.39215... -> ~80
const rate = 100 * currentScore / maxScore;
// Hangi yüzdelik orana girdiğine göre kaç yıldızı aktif edeceğimizi bulalım.
// 100'de rate (80.392...) ise 5'te kaçtır? Oran-orantı... ~4
const activeStar = (5 * rate / 100).toFixed(0);
alert(`Yüzde ${rate.toFixed(2)} puan alarak 5 üzerinden ${activeStar} yıldız aldı`);
Sanırım şurada sizinle aynı soruyu sormuşlar.
https://stackoverflow.com/questions/36052604/how-to-let-react-router-respond-with-404-status-code
Doğru cevap olarak işaretlenmemiş ama şöyle bir çözüm önerilmiş:
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path='post/' component={Post} />
{ /* Catch all route */ }
<Route path="*" component={NotFound} status={404} />
</Route>
Yani 404 sayfanız için status
parametresi gönderin diyor. Sonra da bir middleware yardımıyla status'u 404 olanları yakalamanızı öneriyor.
import { match } from 'react-router';
import getRoutes from './routes';
....
app.use((req, res) => {
match(
{ history, routes: getRoutes(), location: req.originalUrl },
(error, redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search);
}
else if (error) {
console.error('ROUTER ERROR:', error);
res.status(500);
}
else if (renderProps) {
const is404 = renderProps.routes.find(r => r.status === 404) !== undefined;
}
if (is404) {
res.status(404).send('Not found');
}
else {
// Go on and render the freaking component...
}
}
);
});
Eğer siteniz sadece React ise, yani sunucu taraflı bir kontrol edilmeksizin single page application olarak çalışıyorsa zaten 404 dönemez sanıyorum çünkü zaten aslında tüm site tek seferde yüklenmiş oluyor. Yeni yeni sayfalara yeni yeni istekler atılmıyor. Haliyle istek olmayınca yanıt da olmuyor.
Yukarıdaki örnek, react projenizin expressjs üzerinde çalıştığını varsayarak yazılmış.
Eğer siz next.js üzerinde çalışıyorsanız bulamadığı sayfaya zaten 404 dönüyordur diye tahmin ediyorum. (pek bilmem next.js)
Soruyu tam olarak anlamamış olabilirim
Önce Sarı, Mavi, Yeşil'e basıyorsunuz. Yani beden seçimi yapmıyorsunuz.
Sonra bir daha renk seçimi yapmadan S, M, L'ye basıyorsunuz.
Bu durumda son seçtiğiniz renk (Yeşil) için S, M ve L seçilmiştir diyebiliriz.
Ama Sarı ve Mavi için beden seçimi yapmadığınız için onlarda hangi bedenler seçilmiş bilemeyiz.
Anladığım kadarıyla siz sırası önemli olmaksızın seçilen renk ve beden'lerin her türlü kombinasyonunu elde etmek istiyorsunuz.
Yani sadece Sarı seçilirse, sonra da S ve M seçilirse şu kombinasyon oluşsun:
Sarı/S
Sarı/M
Bundan hemen sonra Mavi seçilirse, seçilmiş her beden bu renk için de geçerli olacak:
Sarı/S
Sarı/M
Mavi/S
Mavi/M
Şimdi de mesela L seçilirse seçilmiş her renk için bu beden de geçerli olacak:
Sarı/S
Sarı/M
Sarı/L
Mavi/S
Mavi/M
Mavi/L
Ben soruyu böyle anladım. Cevabım buna göre olacak.
Örnek olarak üstteki veriyi şu şekilde tutacağım:
$_SESSION["combo"] = [
["Sarı", "S"],
["Sarı", "M"],
["Sarı", "L"],
["Mavi", "S"],
["Mavi", "M"],
["Mavi", "L"],
];
Cevapla alakasız öneri (Okumasanız da olur)
Eğer çok yoğun anlık kullanıcıya sahip bir sistem varsa session kullanımını önermiyorum. Çünkü session sunucunın hafızasında yer tutuyor. (Saklandığı yere göre ram'de veya rom'da olabilir)
Bunun yerine çok anlık kullanıcısı olan sistemlerde token'lar kullanılıyor genellikle. Token'lar şifrelenmiş halde istemci tarafında tutulan, şifresinin sadece sunucudaki anahtarla çözülebildiği string değerlerdir. İstemciye token ile şifrelenmiş halde bazı veriler iletilir ve sunucuya yapılan isteklerde mutlaka token'ın da gönderilmesi istenir. Token yoksa işlem yapılmaz. Token sunucudaki şifreyle çözülür ve değerler kontrol edilerek değiştirilip değişitirilmediği kontrol edilir. Değiştirilmişse yine işlem yapılmaz. Token, her isteğe verilen yanıtta değiştirilerek tekrar istemciye gönderilir. İstemci, her yanıtta gelen token'ı eski token'ın yerine kaydeder ve sonraki istekte göndermek için hazırda tutar. Bu yüzden hem front-end tarafında hem de back-end tarafında token yönetimi için oluşturulmuş fonksiyonlar hazırlanmalıdır.
Session kullanımındaki bir diğer sorun da CSRF açığıdır. Örneğin kullanıcınız yönetim panelinde oturum açtı ve bu kullanıcı için bir session oluşturdunuz. Artık bu kullanıcı tarayıcısını kapatana kadar bu session'la işlemler yapma yetkisine sahip oldu. Sonra kullanıcınıza bir e-posta geldi. E-posta'da da "bana tıkla" diye bir link var. Halen aynı tarayıcıyı kullanan kullanıcı bu linke tıkladı. Meğerse link, sizin sitenizdeki yönetim panelinde kullanıcının bazı verilerinin silinebildiği bir sayfaya GET metoduyla giden bir linkmiş. Sonuçta bu sayfaya atılan istek yine aynı tarayıcıdan geldiği için ve sunucu da bu tarayıcıya özel bir session'la hareket ettiği için gelen isteği karşılar ve silme işlemini yapar. Oysa ki sunucu bir token'ın varlığını kontrol ediyor olsaydı e-posta'yı gönderen kişi bu token'ı önceden bilemeyeceği için bu link'i oluşturamazdı. Zaten token'lar get parametresi olarak değil header üzerinden gönderildiği için bu tip link'lere karşı ayrıca bir güvenlik katmanı daha oluştururlar.
Sunucuda session'ın saklandığı hafıza ile ilgili bir sorununuz yoksa, yani sadece CSRF açığını kapatmak istiyorsanız, session ve token'ı birlikte de kullanabilirsiniz tabii ki.
Asıl Cevap
Siz renk veya bedene her tıklandığında sunucuya bir istek atıyorsunuz anladığım kadarıyla. Verileri de session ile tutuyorsunuz. İstiyorsunuz ki her seçiçen renk/beden değeri session içindeki bir PHP objesinde organize şekilde yerleşsin.
// POST metoduyla PHP'ye ilettiğiniz verileri değişkenlere alalım.
$selectedColor = isset($_POST["color"]) ? $_POST["color"] : false;
$selectedSize = isset($_POST["size"]) ? $_POST["size"] : false;
// Ya renk ya da beden seçilmiştir. İkisi birden aynı anda gelemez diye varsayıyorum.
// Eğer ikisi de gelmemişse bir hata mesajı gönderelim.
if(!$selectedColor && !$selectedSize) {
echo "Renk veya beden seçimi iletilmedi!";
exit();
}
// Daha önceden seçilmiş elemanları tuttuğumuz objeyi de bir değişkene alalım.
$currentSelections = isset($_SESSION["combo"]) ? $_SESSION["combo"] : [];
// Daha önce seçilmiş tüm renkleri ve bedenleri bulalım ve iki ayrı dizide tutalım.
$allCurrentColors = [];
$allCurrentSizes = [];
foreach($currentSelections as $selection) {
if(!in_array($selection[0], $allCurrentColors)) $allCurrentColors[] = $selection[0];
if(!in_array($selection[1], $allCurrentSizes)) $allCurrentSizes[] = $selection[1];
}
// Eğer yeni seçilen değer bir renk ise,
if($selectedColor) {
// Ve bu renk zaten daha önce eklenmişse seçilmiş renklerden kaldırılsın. (Bu renk seçimini iptal etmiş oluyoruz)
if(in_array($selectedColor, $allCurrentColors)) {
$allCurrentColors = array_values(array_diff($allCurrentColors,[$selectedColor]));
}
// Yok eğer bu renk daha önce eklenmemişse seçilmiş renklere ekleyelim.
else {
$allCurrentColors[] = $selectedColor;
}
}
// Eğer yeni seçilen değer bir beden ise, yukarıda renk için uyguladığım kontrolün aynısını beden için yapalım.
if($selectedSize) {
if(in_array($selectedSize, $allCurrentSizes)) {
$allCurrentSizes = array_values(array_diff($allCurrentSizes,[$selectedSize]));
}
else {
$allCurrentSizes[] = $selectedSize;
}
}
// Daha önce renk veya beden için null değeri atamış olabiliriz. (Aşağıda yapıyoruz)
// Şu an için bu iki dizide de null değeri bulunmasın.
$allCurrentColors = array_values(array_diff($allCurrentColors,[null]));
$allCurrentSizes = array_values(array_diff($allCurrentSizes,[null]));
// Böylece elimizde iki ayrı dizide seçilmiş tüm renkler ve tüm bedenler mevcut.
// Bunları birleştireceğiz ve yeni $currentSelections değeri ($_SESSION["combo"] değeri) bu birleşim olacak.
// Öncelikle mevcut seçimleri yok edelim.
$currentSelections = [];
// Şu an sadece renk seçilmiş ama hiç beden seçilmemiş olabilir.
// Veya hiç renk seçilmemiştir ama beden seçilmiştir.
// Ama hiç değeri olmayan türü (renk/beden) yeni oluşturacağımız dizide yok sayamayız.
// O yüzden değeri olmayan tür varsa bir tane null değeri atayalım.
if(count($allCurrentColors)===0) $allCurrentColors = [null];
if(count($allCurrentSizes)===0) $allCurrentSizes = [null];
// Eğer iki dizide birden sadece null değerleri kalmışsa iki diziyi de boşaltalım.
if(count($allCurrentColors)===1 && count($allCurrentSizes)===1) {
if($allCurrentColors[0]===null && $allCurrentSizes[0]===null) {
$allCurrentColors = [];
$allCurrentSizes = [];
}
}
// Her bir renk için her bir beden eklenecek.
// Tüm renkleri dönelim. Her bir renk için...
foreach($allCurrentColors as $currentColor) {
// Tüm bedenleri dönelim. Her bir beden için...
foreach($allCurrentSizes as $currentSize) {
$currentSelections[] = [$currentColor, $currentSize];
}
}
// Tüm seçimler yeniden organize olduğuna göre bu yeni değeri session'a yazabiliriz.
$_SESSION["combo"] = $currentSelections;
Kodları hiç kontrol etmedim, tek seferde çalışmayabilir. Hata ayıklamak gerekebilir...
Yazdığım kodun şu şekilde çalışmasını umuyorum:
Sayfaya ilk girildiğinde herhangi bir renk veya beden seçili değil. Bu durumda PHP'de tutulan değer şu:
$_SESSION["combo"] = [];
Sonra M bedeni seçiliyor. İlk seçim. Bu durumda PHP'de tutulan değer:
$_SESSION["combo"] = [
[null, "M"]
];
Sonra L bedeni seçiliyor. 2. seçim de bu. Bu durumda PHP'de tutulan değer:
$_SESSION["combo"] = [
[null, "M"],
[null, "L"]
];
Sonra da Sarı renk seçilmiş olsun. Bu durumda:
$_SESSION["combo"] = [
["Sarı", "M"],
["Sarı", "L"]
];
Sonra da Mavi renk seçilmiş olsun. Bu durumda:
$_SESSION["combo"] = [
["Sarı", "M"],
["Sarı", "L"],
["Mavi", "M"],
["Mavi", "L"]
];
Sonra da tekrar S beden seçilsin.
$_SESSION["combo"] = [
["Sarı", "M"],
["Sarı", "L"],
["Sarı", "S"],
["Mavi", "M"],
["Mavi", "L"],
["Mavi", "S"]
];
Sonra da Yeşil renk seçilmiş olsun.
$_SESSION["combo"] = [
["Sarı", "M"],
["Sarı", "L"],
["Sarı", "S"],
["Mavi", "M"],
["Mavi", "L"],
["Mavi", "S"],
["Yeşil", "M"],
["Yeşil", "L"],
["Yeşil", "S"]
];
Seçimlerinden hoşnut olmamış olacak ki Mavi renk için tekrar tıklamış olsun. Bu durumda:
$_SESSION["combo"] = [
["Sarı", "M"],
["Sarı", "L"],
["Sarı", "S"],
["Yeşil", "M"],
["Yeşil", "L"],
["Yeşil", "S"]
];
Şimdi de L bedenine tekrar basmış olsun. Seçimleri arasında L olmasını istemiyormuş meğerse:
$_SESSION["combo"] = [
["Sarı", "M"],
["Sarı", "S"],
["Yeşil", "M"],
["Yeşil", "S"]
];
Şimdi bütün renk seçimlerini kaldırmak için önce Sarı sonra Yeşil renk butonlarına basmış olsun. Bu 2 istekten sonra:
$_SESSION["combo"] = [
[null, "M"],
[null, "S"],
];
Şimdi beden seçimlerini de kaldırmak için M ve S butonlarına da bastı diyelim. Sonuçta başa dönmüş olacak:
$_SESSION["combo"] = [];
socket.io kullanmalısın. İnternetteki örneklerde hep öyle kullanılıyor ama socket.io sadece chat uygulamaları yapmak için kullanılmaz.
Örnekleri inceleyip socket.io'nun mantığını kavrayabilirsin. Bu sayede sunucuda gerçekleşen bir durumdan istemcilerin haberdar olmalarını sağlayabilirsin. Uzun bir konu olduğu için burada adım adım anlatmak zor.
Merhaba. Etikete PHP girmişsiniz ama front-end tarafında bu sorunu çözebilirsiniz.
Bir butona tıklandığı anda tarihi alır, üzerine 10dk ekler ve bu değeri butona atribute olarak eklersiniz.
Butona her tıklandığında bu attribute'ü kontrol edersiniz ve henüz zamanı gelmemişse işlem yaptırmazsınız.
Örnek olarak (JQuery ile):
const isButtonFree = ($button) => {
const now = (new Date()).getTime();
const afterTenMinutesTime = (new Date(now + 10 * 60 * 1000)).getTime();
const buttonCode = Number($button.attr('data-code'));
if(isNaN(buttonCode) || buttonCode < now) {
$button.attr('data-code', afterTenMinutesTime);
return true;
}
return false;
};
$(document).on("click", "button.sendToUp", function() {
if(!isButtonFree($(this))) { alert("Son tıklamanın üzerinden 10 dk geçmemiş!"); return; }
// Butona tıklanınca normalde yapılan işlemler...
alert("Butona tıklanabildi");
});
Tarayıcıda konsolu kullanmayı bilenler bu işlemi aşamasınlar, diyorsanız ve butona her tıklama işlemi sunucuya bir istek atıyorsa yukarıda verdiğim kodun üstüne bir de PHP tarafında token oluşturmanız ve her butona basışta sunucu tarafında token kontrolü yapmanız gerekir.
Bu mantıkta da PHP tarafında request'e özel bir token üretecek ve response üzerinden bunu tarayıcıya göndereceksiniz.
Tarayıcı bu kodu alıp butona attribute olarak basacak.
Butona her basışta attribute üzerinden bu token değeri okunacak ve request'e eklenecek.
Bu token, içinde 10 dk sonrasının verisini şifreli şekilde tutan ve şifrenin anahtarını bilmeyenin şifreyi çözemeyeceği bir kod olmalı.
Bu konu, Tayfun ERBİLEN'in PHP ile Verileri Şifrelemek makalesinden yardım alınarak çözülebilir.
Şimdi diyelim ki siz butona basıldığında işlem yapıp yapmayacağınızı belirlemek için MoveUp.php sayfasına post isteği atıyorsunuz ve yanıta göre işlem yapıp yapmayacağınıza karar veriyorsunuz.
MoveUp.php
// Aynı kodları tekrar tekrar yazmamak için bir fonksiyon...
function sendResponse($val) {
header("Content-type: application/json; charset=utf-8");
echo json_encode($val);
exit();
}
// Token şifreleme algoritması ve şifre anahtarı verileri...
$tokenKey = 'prototurk.2022.xx1';
$tokenCipher = 'AES-128-ECB';
// POST parametrelerini alalım.
$token = isset($_POST["token"]) ? $_POST["token"] : null;
$id = isset($_POST["id"]) ? $_POST["id"] : null; // başka parametreler de göndermişsiniz diyelim...
// token veya id değeri yoksa hata döndürelim.
if(!$token) sendResponse(["error"=>"token required", "errorCode"=>1]);
if(!$id) sendResponse(["error"=>"id required", "errorCode"=>1]);
// token çözülememişse (değiştirilmiştir muhtemelen) hata dönelim.
$decodedToken = openssl_decrypt($token, $tokenCipher, $tokenKey);
if(!$decodedToken) sendResponse(["error"=>"token hatalı", "errorCode"=>2]);
// token içindeki değer bir timestamp değeri. Bu değeri şu anın timestamp'iyle karşılaştıralım.
// Eğer henüz token'daki tarih şu anki tarihten önce değilse henüz 10 dakikanın geçmediğine dair hata dönelim.
$tokenTime = (int)$decodedToken;
$now = strtotime("now");
if($now < $tokenTime) sendResponse(["error"=>"It hasn't been 10 minutes.", "errorCode"=>3);
// Buraya kadar her şey yolundaysa butonun 10 dakikası geçmiş demektir.
// Şu andan itibaren 10 dakika sonrasını bulup şifreleyelim.
$afterTenMinutes = strtotime("+10 minute");
$newToken = openssl_encrypt($afterTenMinutes, $tokenCipher, $tokenKey);
// Bundan sonra id gibi diğer parametreleri kullanarak gerekli işlemlerimizi yapıyoruz.
// Sonuç olarak döneceğimiz cevapta mutlaka yeni token değerini de gönderiyoruz ki butonun attribute'sine yeni token değeri yazılabilsin.
sendResponse["success"=>"It can move to up!", "token"=>$newToken];
Bu sayfa sizin butona basıldıktan sonra sunucuya istek attığınız ve sunucu taraflı işleri hallettiğiniz sayfa.
Bu sayfaya POST metoduyla "token" değerini göndermezseniz veya değiştirilmiş bir token gönderirseniz size hata verir.
Haliyle butonlar ilk oluştukları zamanda herhangi bir token değerine sahip olmadıkları için bu sayfaya atacağınız her istek hata döner.
Bu nedenle butonları oluştururken hemen token'larını da vermelisiniz.
Örneğin butonlarınızın bulunduğu front-end sayfanız şöyle olacak:
<?php
// Token şifreleme algoritması ve şifre anahtarı verileri...
$tokenKey = 'prototurk.2022.xx1';
$tokenCipher = 'AES-128-ECB';
$firstToken = openssl_encrypt(strtotime("+10 minute"), $tokenCipher, $tokenKey);
?>
<div class="moveableElement" data-id="123">
<div class="elementTitle">Örnek içerik 1</div>
<button class="moveToUpButton" data-token="<?=$firstToken?>">Yukarı Taşı</button>
</div>
<div class="moveableElement" data-id="132">
<div class="elementTitle">Örnek içerik 2</div>
<button class="moveToUpButton" data-token="<?=$firstToken?>">Yukarı Taşı</button>
</div>
<div class="moveableElement" data-id="231">
<div class="elementTitle">Örnek içerik 3</div>
<button class="moveToUpButton" data-token="<?=$firstToken?>">Yukarı Taşı</button>
</div>
Bu örnekte sayfaya girildiği andan itibaren tüm butonlar, 10 dakika sonrasının şifrelenmiş halini token olarak almış olacaklar.
Yani biri sayfaya ilk kez girerse veya eskaza sayfayı yenilerse 10 dk boyunca yukarı taşıma yapamayacak.
Şimdi daha önce front-end taraflı kontrol sağladığımız kodda istek atma örneği yapalım.
Diyelim ki butona basınca üst elementin data-id
attribute'sini alıp veriyi PHP'ye ajax ile iletiyoruz...
const isButtonFree = ($button) => {
const now = (new Date()).getTime();
const afterTenMinutesTime = (new Date(now + 10 * 60 * 1000)).getTime();
const buttonCode = Number($button.attr('data-code'));
if(isNaN(buttonCode) || buttonCode < now) {
$button.attr('data-code', afterTenMinutesTime);
return true;
}
return false;
};
$(document).on("click", "button.sendToUp", function() {
const $thisButton = $(this);
if(!isButtonFree($thisButton)) { alert("Son tıklamanın üzerinden 10 dk geçmemiş!"); return; }
const itemId = $(this).closest(".moveableElement").attr("data-id");
const itemToken = $(this).attr("data-token");
$.ajax({
url:"MoveUp.php",
type:"POST",
data: { id:itemId, token:itemToken },
success: (res) => {
// Eğer sunucu token göndermişse butondaki data-token'ı hemen güncelleyelim.
if(res.token) $thisButton.attr('data-token', res.token);
if(res.error) {
if(res.errorCode===1) alert("eksik parametre");
else if(res.errorCode===2) alert("token hatalı! Sayfayı yenileyin");
else if(res.errorCode===3) alert("Henüz 10 dakika geçmemiş...");
return;
}
if(res.success) {
alert("Tamam. Yukarı Taşıma işlemi yapılabilir...");
return;
}
},
error: (err) => {
alert("Sunucuyla iletişimde problem...");
}
});
});
Muhakkak sizin yapmak istediğiniz şeyler biraz daha farklıdır. Ama bu cevaptan yola çıkarak fikir yürütebilirsiniz. Bundan daha detaylı cevap alabileceğinizi sanmıyorum. :)
Aslında ilk yazdığım kod bloğundaki işlemler çoğu kullanıcının sürekli Yukarı Taşı yapmasını önleyecektir.
PHP taraflı kontrollerimizle kötü niyetli kullanıcıların bir çoğunu engellemiş olduk.
Peki kötü niyetli bir kullanıcı bu token'la kurduğumuz güvenlik katmanını aşamaz mı? Yeterince tecrübeliyse kolaylıkla aşabilir. Mesela sayfa ilk yüklendiğinde butona verilen token'ı kopyalar. Sonra ilk Yukarı Taşıma işleminden sonra biz javascript'le yeni token'ı butona veririz. Evet bu token'ı silerse veya değiştirirse işlem yapamaz ama ilk kopyaladığı token halen PHP tarafından kabul edilen bir token olduğuna göre bu token'ı istediği butona yapıştırıp tekrar tekrar Yukarı Taşıma işlemi yapabilir. Peki bu kötü niyetli kullanıcı için ne yapabiliriz? Mesela şifreleme yaparken token'a sadece tarih verisini değil aynı zamanda data-id attribute'sindeki değeri de katarız. Böylece her butonun kendine özgü bir token'ı olur. MoveUp.php sayfası da hem tarihi hem de id değerini birlikte konrol eder. Peki böyle yaparak her butonun kendine özgü bir token'ı olmasını sağlamak bu kötü niyetli kullanıcıyı durdurabilir mi? Hayır. Sadece aynı kodu istediği her butonda kullanamamasını sağlamış oluruz. Ama her butonun token'ını yine de kopyala yapıştırla yeniden kullanabilir. Demek ki bir token'ın kullanıldıktan sonra tekrar kullanılamamasını sağlayacak bir algoritmamız olması lazım...
Bunlar daha derinlikli siber güvenlik konuları olduğundan bambaşka bir soru'nun konusu olabilir...
Bir kez daha bakınca aslında daha kısa SQL oluşmasını sağlayabilirmişiz gibi göründü.
Mesela ilk foreach
döngüsünde oluşturup $whereCondition
değişkenine atadığımız string örnek olarak şöyle:
(orderId='123' auth='abc') OR (orderId='131' auth='abc') OR (orderId='213' auth='abc')
yani aslında auth sütunu hep aynı. O halde bunu tek bir AND ifadesiyle birleştirebilirdik. Yani:
auth='abc' AND (orderId='123' OR orderId='131' OR orderId='213')
şeklinde yazabilirdik. Muhtemelen az da olsa MySQL için de daha rahat bir sorgu olacaktır. İki sütunu her OR
bloğu için kontrol etmemiş olur.
Bu durumda aşağıdaki şu satırları:
foreach ($detail as $keys) {
$whereConditions[] = "(orderId='" . $keys["orderId"] . "' AND auth='" . $this->appkey . "')";
$whereConditionsWithESD[] = "(ESD.orderId='" . $keys["orderId"] . "' AND ESD.auth='" . $this->appkey . "')";
}
$whereCondition = implode(" OR ", $whereConditions);
$whereConditionWithESD = implode(" OR ", $whereConditionsWithESD);
şu şekilde değiştirmek daha yerinde olur:
foreach ($detail as $keys) {
$orderId = $keys["orderId"];
$whereConditions[] = "orderId='$orderId'";
$whereConditionsWithESD[] = "ESD.orderId='$orderId'";
}
$whereCondition = implode(" OR ", $whereConditions);
$whereConditionWithESD = implode(" OR ", $whereConditionsWithESD);
$appKey = $this->appkey;
$whereCondition = "auth='$appKey' AND ($whereCondition)";
$whereConditionWithESD = "ESD.auth='$appKey' AND ($whereCondition)";
Böylece satır sayısı biraz arttı ama MySQL için daha rahat bir sorgu oluşturduk.
Aşağıdaki satırlarda sanırım $orderList
objesindeki değer kodunuzdaki diğer kurgularla uyuşmadığı için yeni bir formata çevirerek orderId değerlerine göre gruplamışsınız.
O kısmı şu şekilde değiştirirseniz daha kısa kodla işi çözebilirsiniz sanıyorum.
Yani yukarıdaki kodlardan sonra gelen kodlarınızdaki $orderListESD = [];
ile başlayıp $orderList = $orderListESD;
ile biten kısmı komple silip yerine şunu yazabilirsiniz:
$relationalOrderList = [];
foreach($orderList["detail"] as $detail) {
$orderId = $detail["orderId"];
$relationalOrderList[] = [
"detail" => $detail,
"address" => array_filter($orderList["address"], function($item){ return $item["orderId"]===$orderId; }),
"cargoDetail" => array_filter($orderList["cargoDetail"], function($item){ return $item["orderId"]===$orderId; }),
"customerDetail" => array_filter($orderList["customerDetail"], function($item){ return $item["orderId"]===$orderId; }),
"list" => array_filter($orderList["list"], function($item){ return $item["orderId"]===$orderId; }),
];
}
$orderList = $relationalOrderList;
<div>
<input type="hidden" name="content_id" value="123">
<input type="text" name="content_detail" class="form-control" value="abc1">
</div>
<div>
<input type="hidden" name="content_id" value="132">
<input type="text" name="content_detail" class="form-control" value="abc2">
</div>
<div>
<input type="hidden" name="content_id" value="213">
<input type="text" name="content_detail" class="form-control" value="abc3">
</div>
<div>
<input type="hidden" name="content_id" value="231">
<input type="text" name="content_detail" class="form-control" value="abc4">
</div>
Bu şekilde input'lar var ve data
key'iyle post ediliyorlar.
content_id ve content_detail dizi olarak PHP'ye iletiliyor ve bu dizilerin eleman sayıları aynı.
HTML'de de doğru sıralamayla dizilmişlerse yukarıdaki html koduna benzer yapıdan giden POST isteğinin PHP'de şu şekilde bir karşılığı oluşuyor olmalı:
$_POST["data"]["content_id"] = ["123", "132", "213", "231"];
$_POST["data"]["content_detail"] = ["abc1", "abc2", "abc3", "abc4"];
Bunları tek bir çatı altında birleştirmek istiyorsanız:
$content = [];
$contentCount = count($_POST["data"]["content_id"]);
for($i=0; $i < $contentCount; $i++) {
$content[] = [
"id" => $_POST["data"]["content_id"][$i],
"detail" => $_POST["data"]["content_detail"][$i]
];
}
echo "<pre>";
print_r($content);
echo "</pre>";
Böylece $content
değişkeninde verileri toplamış oldunuz.