Compare commits
No commits in common. "main" and "main" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,3 @@ secrets
|
||||
secrets/
|
||||
secrets/config.php
|
||||
qodana.yaml
|
||||
*.br
|
||||
.br
|
||||
google*.html
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"esversion": 10
|
||||
}
|
8280
assets/3rdparty/fonts/remixicon/remixicon.css
vendored
8280
assets/3rdparty/fonts/remixicon/remixicon.css
vendored
File diff suppressed because one or more lines are too long
4
assets/3rdparty/pico.min.css
vendored
4
assets/3rdparty/pico.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB |
763
assets/script.js
763
assets/script.js
@ -1,113 +1,33 @@
|
||||
let UserInfo = {};
|
||||
let PageIntervals = [];
|
||||
let config =
|
||||
{
|
||||
articleRefresh: 300000,
|
||||
messageFadeIn: 300,
|
||||
messageDisappear: 200
|
||||
};
|
||||
|
||||
function isLoggedIn() {
|
||||
"use strict";
|
||||
return UserInfo.Email && UserInfo.Email.length > 0;
|
||||
}
|
||||
|
||||
async function setElementClasses(element, newClasses) {
|
||||
// Ensure the element exists
|
||||
if (!element) return;
|
||||
|
||||
// Clear all existing classes
|
||||
element.className = '';
|
||||
|
||||
// Add the new classes to the element
|
||||
element.classList.add(...newClasses);
|
||||
}
|
||||
|
||||
async function handleResponse(data, successMessage, failureMessage) {
|
||||
"use strict";
|
||||
const statusMessageContainer = document.getElementById("statusMessageContainer");
|
||||
|
||||
const statusMessage = document.createElement("div");
|
||||
statusMessage.classList.add("status-message");
|
||||
|
||||
if (data.Status === 'Success') {
|
||||
statusMessage.innerText = successMessage;
|
||||
statusMessage.classList.add("success");
|
||||
} else {
|
||||
statusMessage.innerText = failureMessage;
|
||||
statusMessage.classList.add("failure");
|
||||
}
|
||||
|
||||
statusMessageContainer.appendChild(statusMessage);
|
||||
|
||||
// Automatically remove the message after 3 seconds
|
||||
setTimeout(() => {
|
||||
statusMessage.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
statusMessage.remove();
|
||||
}, config.messageFadeIn);
|
||||
}, config.messageDisappear);
|
||||
}
|
||||
|
||||
async function showDashboardGreeting() {
|
||||
"use strict";
|
||||
document.getElementById("welcomeMsg").innerText = `Ahoj, ${UserInfo.FirstName}.`;
|
||||
}
|
||||
|
||||
async function doAction(url, requestData, successMessage, failureMessage, silent) {
|
||||
"use strict";
|
||||
const params = new FormData();
|
||||
|
||||
for (const key in requestData) {
|
||||
params.append(key, requestData[key]);
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
function doAccountAction(requestData, successMessage, failureMessage, silent=false) {
|
||||
return fetch('/account', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!silent) {
|
||||
await handleResponse(data, successMessage, failureMessage);
|
||||
}
|
||||
|
||||
return data;
|
||||
body: requestData,
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if(!silent) {
|
||||
handleResponse(data, successMessage, failureMessage);
|
||||
}
|
||||
return data; // Returning the response data for further processing
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
async function handlePageResponse(data) {
|
||||
"use strict";
|
||||
const navbar = document.getElementById("navbar_container");
|
||||
const pageArea = document.getElementById("page_container");
|
||||
|
||||
if (data.Navigation) {
|
||||
navbar.innerHTML = data.Navigation;
|
||||
}
|
||||
if (data.PageTitle) {
|
||||
document.title = data.PageTitle;
|
||||
}
|
||||
|
||||
if (data.Page) {
|
||||
pageArea.innerHTML = data.Page;
|
||||
if (data.PageLocation) {
|
||||
history.pushState({}, "", data.PageLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function displayList(data, elementId, deleteFunction) {
|
||||
"use strict";
|
||||
const tableContainer = document.getElementById(elementId);
|
||||
function displayList(data, element_id, delete_function=null) {
|
||||
const tableContainer = document.getElementById(element_id);
|
||||
tableContainer.innerHTML = ""; // Clear previous content
|
||||
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("list-table");
|
||||
|
||||
// Create header row
|
||||
const headerRow = table.insertRow(0);
|
||||
for (const key in data[0]) {
|
||||
const th = document.createElement("th");
|
||||
@ -115,14 +35,13 @@ async function displayList(data, elementId, deleteFunction) {
|
||||
headerRow.appendChild(th);
|
||||
}
|
||||
|
||||
if (typeof deleteFunction === "function") {
|
||||
if(typeof delete_function === "function") {
|
||||
const th = document.createElement("th");
|
||||
let deleteBtn = document.createElement('i');
|
||||
deleteBtn.classList.add("ri-delete-bin-line");
|
||||
th.appendChild(deleteBtn);
|
||||
th.appendChild(document.createTextNode("Delete"));
|
||||
headerRow.appendChild(th);
|
||||
}
|
||||
|
||||
// Create data rows
|
||||
for (const line of data) {
|
||||
const dataRow = table.insertRow();
|
||||
for (const key in line) {
|
||||
@ -130,12 +49,14 @@ async function displayList(data, elementId, deleteFunction) {
|
||||
td.appendChild(document.createTextNode(line[key]));
|
||||
dataRow.appendChild(td);
|
||||
}
|
||||
if (typeof deleteFunction === "function") {
|
||||
if(typeof delete_function === "function") {
|
||||
const td = document.createElement("td");
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.innerHTML = "<i class='ri-delete-bin-line'></i>";
|
||||
deleteButton.onclick = () => deleteFunction(line.ID);
|
||||
td.appendChild(deleteButton);
|
||||
let delete_button = document.createElement('button');
|
||||
delete_button.textContent = "Delete"
|
||||
delete_button.onclick = function (){
|
||||
delete_function(line.ID);
|
||||
}
|
||||
td.appendChild(delete_button);
|
||||
dataRow.appendChild(td);
|
||||
}
|
||||
}
|
||||
@ -143,609 +64,35 @@ async function displayList(data, elementId, deleteFunction) {
|
||||
tableContainer.appendChild(table);
|
||||
}
|
||||
|
||||
async function doPageAction(requestData) {
|
||||
"use strict";
|
||||
const response = await fetch('/page', {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams(requestData),
|
||||
});
|
||||
function handleResponse(data, SuccessMessage, failureMessage) {
|
||||
const StatusMessageElement = document.getElementById("StatusMessage");
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
await handlePageResponse(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async function initAjaxNavigationEvents() {
|
||||
"use strict";
|
||||
const allLinks = document.querySelectorAll('.navsite_link, .navpage_link');
|
||||
const pageLinks = document.querySelectorAll('.navpage_link');
|
||||
|
||||
pageLinks.forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
navLinks.classList.remove("active");
|
||||
});
|
||||
});
|
||||
|
||||
allLinks.forEach(function (link) {
|
||||
link.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
let site = this.dataset.site;
|
||||
let page = this.dataset.page;
|
||||
if (site && page) {
|
||||
navigateTo(site, page);
|
||||
}
|
||||
});
|
||||
});
|
||||
const toggleButton = document.getElementById("toggle_button");
|
||||
const navLinks = document.getElementById("navsite_list");
|
||||
|
||||
toggleButton.addEventListener('click', () => {
|
||||
navLinks.classList.toggle("active");
|
||||
});
|
||||
}
|
||||
|
||||
async function initAjax() {
|
||||
"use strict";
|
||||
|
||||
await initAjaxNavigationEvents();
|
||||
await onPageLoad();
|
||||
}
|
||||
|
||||
async function togglearticlecreate() {
|
||||
"use strict";
|
||||
let articleContainerElement = document.getElementById("articlecreatecontainer");
|
||||
|
||||
articleContainerElement.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
async function togglememecreate() {
|
||||
"use strict";
|
||||
let memeContainerElement = document.getElementById("memecreatecontainer");
|
||||
|
||||
await getMemeImages();
|
||||
memeContainerElement.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
async function renderarticles() {
|
||||
"use strict";
|
||||
let template = document.querySelector('template[data-template-name="article"]').innerHTML;
|
||||
let articles = await doAction(
|
||||
"/newsarticle",
|
||||
{
|
||||
action: "getNewsArticles"
|
||||
},
|
||||
"Články načítané",
|
||||
"Nastala chyba pri načítavaní článkov",
|
||||
true
|
||||
);
|
||||
|
||||
let articleout = "";
|
||||
for (const article of articles.Articles) {
|
||||
articleout += template.replace("__TEMPLATE_ARTICLE_TITLE__", article.Title).replace("__TEMPLATE_ARTICLE_AUTHOR__", article.WrittenByName).replace("__TEMPLATE_ARTICLE_DATE__", article.WrittenAt).replace("__TEMPLATE_ARTICLE_BODY__", article.Body);
|
||||
}
|
||||
document.getElementById("articleslist").innerHTML = articleout;
|
||||
}
|
||||
|
||||
async function submitarticle() {
|
||||
"use strict";
|
||||
let articleTitleElement = document.getElementById("articletitleinput");
|
||||
let articleBodyElement = document.getElementById("articlebodyinput");
|
||||
await doAction(
|
||||
"/newsarticle",
|
||||
{
|
||||
action: "addNewsArticle",
|
||||
title: articleTitleElement.value,
|
||||
body: articleBodyElement.value
|
||||
},
|
||||
"Článok úspešne pridaný",
|
||||
"Nastala chyba pri pridávaní článku",
|
||||
false
|
||||
);
|
||||
await togglearticlecreate();
|
||||
}
|
||||
|
||||
async function articleInit() {
|
||||
"use strict";
|
||||
let articleContainerElement = document.getElementById("articlecreatecontainer");
|
||||
let articleCreateOpenElement = document.getElementById("articlecreateopen");
|
||||
articleContainerElement.addEventListener("keyup", function (ev) {
|
||||
if (ev.key === "Escape") {
|
||||
togglearticlecreate();
|
||||
}
|
||||
});
|
||||
PageIntervals.push(setInterval(renderarticles, config.articleRefresh));
|
||||
document.getElementById("articleprivilegeinput").setAttribute("max", UserInfo.Privileges);
|
||||
if (UserInfo.Privileges < 2) {
|
||||
articleContainerElement.style.display = "none";
|
||||
articleCreateOpenElement.style.display = "none";
|
||||
if (data.Status === 'Success') {
|
||||
StatusMessageElement.innerText = SuccessMessage;
|
||||
} else {
|
||||
articleCreateOpenElement.style.display = "inline-block";
|
||||
StatusMessageElement.innerText = failureMessage;
|
||||
}
|
||||
|
||||
// Show the status message
|
||||
StatusMessageElement.style.display = "block";
|
||||
setTimeout(() => {
|
||||
// Hide the status message after 3 seconds
|
||||
StatusMessageElement.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
StatusMessageElement.style.display = "none";
|
||||
// Reset opacity for future messages
|
||||
StatusMessageElement.style.opacity = "1";
|
||||
}, 500);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
async function onPageLoad() {
|
||||
"use strict";
|
||||
await restoreUserInfo();
|
||||
let currentSite = localStorage.getItem("currentSite");
|
||||
let currentPage = localStorage.getItem("currentPage");
|
||||
function logout() {
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "logout");
|
||||
|
||||
for (let interval of PageIntervals) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
if (currentSite === "account" && currentPage === "settings") {
|
||||
if (document.getElementById("user-settings")) {
|
||||
await populateUserInfoFields(UserInfo);
|
||||
}
|
||||
if (document.getElementById("admin-settings")) {
|
||||
await listActivationCodes(true);
|
||||
await listUsers(true);
|
||||
}
|
||||
}
|
||||
if (currentSite === "account" && currentPage === "index" && isLoggedIn()) {
|
||||
await showDashboardGreeting();
|
||||
}
|
||||
if (currentSite === "news" && currentPage === "index") {
|
||||
await articleInit();
|
||||
}
|
||||
if (currentSite === "account" && currentPage === "files") {
|
||||
await listFiles();
|
||||
}
|
||||
if (currentSite === "memes" && currentPage === "index") {
|
||||
await getMemeImages();
|
||||
}
|
||||
}
|
||||
|
||||
async function navigateTo(site, page) {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "getPage",
|
||||
site: site,
|
||||
page: page,
|
||||
};
|
||||
doPageAction(data).then(() => {
|
||||
localStorage.setItem("currentSite", site);
|
||||
localStorage.setItem("currentPage", page);
|
||||
onPageLoad();
|
||||
doAccountAction(data, "Logout Successful!", "Logout failed.").then(() => {
|
||||
location.reload();
|
||||
// Expected output: "Success!"
|
||||
});
|
||||
}
|
||||
|
||||
async function softReload() {
|
||||
"use strict";
|
||||
let currentSite = localStorage.getItem("currentSite");
|
||||
let currentPage = localStorage.getItem("currentPage");
|
||||
await navigateTo(currentSite, currentPage);
|
||||
umami.track("softReload");
|
||||
}
|
||||
|
||||
async function refreshNavbar() {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "getNavigation",
|
||||
};
|
||||
await doPageAction(data);
|
||||
umami.track("refreshNavbar");
|
||||
await initAjaxNavigationEvents();
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "logout",
|
||||
};
|
||||
|
||||
doAction('/account', data, "Logout Successful!", "Logout failed.", false)
|
||||
.then(async () => {
|
||||
await refreshNavbar();
|
||||
await navigateTo(localStorage.getItem("defaultSite"), localStorage.getItem("defaultPage"));
|
||||
localStorage.clear();
|
||||
UserInfo = {};
|
||||
umami.track("logout");
|
||||
})
|
||||
.catch((error) => {
|
||||
// Handle errors if needed
|
||||
console.error("An error occurred during logout:", error);
|
||||
});
|
||||
}
|
||||
|
||||
async function login() {
|
||||
"use strict";
|
||||
const email = document.getElementById("login_email").value;
|
||||
const password = document.getElementById("login_password").value;
|
||||
await doLogin(email, password);
|
||||
await getUserInfo();
|
||||
await refreshNavbar();
|
||||
await softReload();
|
||||
}
|
||||
|
||||
async function doLogin(email, password) {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "login",
|
||||
email: email,
|
||||
password: password,
|
||||
};
|
||||
|
||||
await doAction('/account', data, "Login Successful!", "Login failed. Please check your credentials.", false);
|
||||
umami.track("login");
|
||||
}
|
||||
|
||||
async function register() {
|
||||
"use strict";
|
||||
const firstName = document.getElementById("register_firstName").value;
|
||||
const lastName = document.getElementById("register_lastName").value;
|
||||
const email = document.getElementById("register_email").value;
|
||||
const password = document.getElementById("register_password").value;
|
||||
const activationToken = document.getElementById("register_activationToken").value;
|
||||
|
||||
const data = {
|
||||
action: "register",
|
||||
firstname: firstName,
|
||||
lastname: lastName,
|
||||
email: email,
|
||||
password: password,
|
||||
activation_token: activationToken,
|
||||
};
|
||||
|
||||
await doRegister(data);
|
||||
}
|
||||
|
||||
async function doRegister(requestData) {
|
||||
"use strict";
|
||||
await doAction('/account', requestData, "Registration Successful!", "Registration failed.", false);
|
||||
umami.track("register");
|
||||
}
|
||||
|
||||
//User settings start
|
||||
|
||||
async function changePassword() {
|
||||
"use strict";
|
||||
const oldPassword = document.getElementById("changeOldPassword").value;
|
||||
const newPassword = document.getElementById("changeNewPassword").value;
|
||||
|
||||
const data = {
|
||||
action: "change_password",
|
||||
old_password: oldPassword,
|
||||
new_password: newPassword,
|
||||
};
|
||||
|
||||
await doChangePassword(data, "Password change Successful!", "Password change failed.");
|
||||
}
|
||||
|
||||
async function doChangePassword(requestData, successMessage, failureMessage) {
|
||||
"use strict";
|
||||
await doAction('/account', requestData, successMessage, failureMessage, false);
|
||||
umami.track("passwordChange");
|
||||
}
|
||||
|
||||
async function updateUserProfile() {
|
||||
"use strict";
|
||||
const firstName = document.getElementById("updateFirstName").value;
|
||||
const lastName = document.getElementById("updateLastName").value;
|
||||
const nickname = document.getElementById("updateNickname").value;
|
||||
const minecraftNick = document.getElementById("updateMinecraftNick").value;
|
||||
|
||||
const data = {
|
||||
action: "update_user_profile",
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
nickname: nickname,
|
||||
minecraft_nick: minecraftNick,
|
||||
};
|
||||
|
||||
await doAction('/account', data, "Profile update Successful!", "Profile update failed.", false);
|
||||
umami.track("updateUserProfile");
|
||||
}
|
||||
|
||||
async function updateEmail() {
|
||||
"use strict";
|
||||
const newEmail = document.getElementById("updateNewEmail").value;
|
||||
|
||||
const data = {
|
||||
action: "update_user_email",
|
||||
email: newEmail,
|
||||
};
|
||||
|
||||
await doAction('/account', data, "Email update Successful!", "Email update failed.", false);
|
||||
umami.track("updateEmail");
|
||||
}
|
||||
|
||||
async function populateUserInfoFields(userData) {
|
||||
"use strict";
|
||||
document.getElementById("updateFirstName").value = userData.FirstName || "";
|
||||
document.getElementById("updateLastName").value = userData.LastName || "";
|
||||
document.getElementById("updateNickname").value = userData.Nickname || "";
|
||||
document.getElementById("updateMinecraftNick").value = userData.MinecraftNick || "";
|
||||
document.getElementById("updateNewEmail").value = userData.Email || "";
|
||||
}
|
||||
|
||||
async function restoreUserInfo() {
|
||||
"use strict";
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
let key = localStorage.key(i);
|
||||
if (key.startsWith("UserInfo_")) {
|
||||
let keyClean = key.replace("UserInfo_", "");
|
||||
UserInfo[keyClean] = localStorage.getItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getUserInfo() {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "get_user_info",
|
||||
};
|
||||
const result = await doAction('/account', data, "User info retrieved Successfully!", "User info retrieval failed.", true);
|
||||
|
||||
if (result && result.Status === "Success") {
|
||||
Object.keys(result.UserInfo).forEach(index => {
|
||||
let value = result.UserInfo[index];
|
||||
localStorage.setItem("UserInfo_" + index, value);
|
||||
UserInfo[index] = value;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//User settings end
|
||||
|
||||
//Admin settings start
|
||||
|
||||
async function addActivationCodes() {
|
||||
"use strict";
|
||||
const count = document.getElementById("activationCodeCount").value;
|
||||
|
||||
const data = {
|
||||
action: "add_activation_codes",
|
||||
count: count,
|
||||
};
|
||||
|
||||
doAction('/account', data, "Activation codes added Successfully!", "Activation codes addition failed.", false).then((result) => {
|
||||
displayList(result.ActivationCodes, "codeListTable", deleteActivationCode);
|
||||
umami.track("addActivationCodes");
|
||||
});
|
||||
}
|
||||
|
||||
async function listUsers(silent) {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "list_users",
|
||||
};
|
||||
|
||||
doAction('/account', data, "User list retrieved Successfully!", "User list retrieval failed.", silent).then((result) => {
|
||||
|
||||
if (result && result.Status === "Success") {
|
||||
displayList(result.Users, "userListTable", deleteUser);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function listActivationCodes(silent) {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "list_activation_codes",
|
||||
};
|
||||
|
||||
doAction('/account', data, "Activation code list retrieved Successfully!", "Activation code list retrieval failed.", silent).then((result) => {
|
||||
displayList(result.ActivationCodes, "codeListTable", deleteActivationCode);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteUser(userId) {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "delete_user",
|
||||
user_id: userId,
|
||||
};
|
||||
|
||||
await doAction('/account', data, "User deleted Successfully!", "User deletion failed.", false);
|
||||
await listUsers(false);
|
||||
umami.track("deleteUser");
|
||||
}
|
||||
|
||||
async function deleteActivationCode(activationCode) {
|
||||
"use strict";
|
||||
const data = {
|
||||
action: "delete_activation_code",
|
||||
activation_code: activationCode,
|
||||
};
|
||||
|
||||
await doAction('/account', data, "Activation code deleted Successfully!", "Activation code deletion failed.", false);
|
||||
await listActivationCodes(false);
|
||||
umami.track("deleteActivationCode");
|
||||
}
|
||||
|
||||
//Admin settings end
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initAjax);
|
||||
} else {
|
||||
setTimeout(initAjax, 0);
|
||||
}
|
||||
|
||||
async function uploadFile() {
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const fileForm = document.getElementById('uploadForm');
|
||||
let data = {
|
||||
action: "uploadFiles"
|
||||
};
|
||||
for (let i = 0; i < fileInput.files.length; i++) {
|
||||
data[`userFile${i}`] = fileInput.files[i];
|
||||
}
|
||||
await doAction("/upload", data, "Súbor bol úspešne nahraný", "Nastala chyba pri nahrávaní súboru", false);
|
||||
fileForm.reset();
|
||||
await listFiles();
|
||||
}
|
||||
|
||||
async function deleteFile(fileID) {
|
||||
await doAction("/upload", {
|
||||
action: "deleteFile",
|
||||
file_id: fileID
|
||||
}, "Súbor bol úspešne zmazaný", "Nastala chyba pri mazaní súboru", true);
|
||||
await listFiles();
|
||||
}
|
||||
|
||||
async function getFileList() {
|
||||
const resp = await doAction("/upload", {
|
||||
action: "getAllFiles"
|
||||
}, "Zoznam súborov bol úspešne stiahnutý", "Nastala chyba pri sťahovaní zoznamu súborov", true);
|
||||
|
||||
if (resp.Status === "Success") {
|
||||
return resp.Files;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function listFiles() {
|
||||
const fileList = await getFileList();
|
||||
if (fileList) {
|
||||
await displayList(fileList, "filelist", deleteFile);
|
||||
}
|
||||
}
|
||||
|
||||
async function addMeme() {
|
||||
let memeTitleElement = document.getElementById("meme_title_input");
|
||||
let memeTextElement = document.getElementById("meme_text_input");
|
||||
let memeImageElement = document.getElementById("meme_image_input");
|
||||
|
||||
await doAction("/meme", {
|
||||
action: "addMeme",
|
||||
meme_title: memeTitleElement.value,
|
||||
meme_text: memeTextElement.value,
|
||||
meme_image_id: memeImageElement.value
|
||||
}, "Meme bol zmazaný", "Nastala chyba pri mazaní meme-u", false);
|
||||
memeTitleElement.value = "";
|
||||
memeTextElement.value = "";
|
||||
memeImageElement.selectedIndex = 0
|
||||
await togglememecreate();
|
||||
}
|
||||
|
||||
async function deleteMeme(memeId) {
|
||||
await doAction("/meme", {
|
||||
action: "deleteMeme",
|
||||
meme_id: memeId
|
||||
}, "Meme bol zmazaný", "Nastala chyba pri mazaní meme-u", false);
|
||||
await softReload();
|
||||
}
|
||||
|
||||
async function getMemeImages() {
|
||||
let memeImageSelector = document.getElementById("meme_image_input");
|
||||
let fileList = await getFileList();
|
||||
fileList.forEach((item) => {
|
||||
let option = document.createElement("option");
|
||||
option.value = item.ID;
|
||||
let splitPath = item.Path.split("/");
|
||||
option.text = `${splitPath[splitPath.length - 1]} - ID: (${item.ID}) Autor: [${item.UploadedBy} (${item.UploadedByID})]`;
|
||||
memeImageSelector.appendChild(option);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async function reloadMemeVotes(memeID) {
|
||||
let memeVoteCounterElement = document.getElementById(`meme_votes_counter_${memeID}`);
|
||||
let memeVoteUpvoteElement = document.getElementById(`meme_votes_upvote_${memeID}`);
|
||||
let memeVoteDownvoteElement = document.getElementById(`meme_votes_downvote_${memeID}`);
|
||||
let memeVoteUpvoteButtonElement = document.getElementById(`meme_votes_upvote_button_${memeID}`);
|
||||
let memeVoteDownvoteButtonElement = document.getElementById(`meme_votes_downvote_button_${memeID}`);
|
||||
|
||||
|
||||
let memeVoteResponse = await doAction('/meme', {
|
||||
action: "getMemeVotes",
|
||||
meme_id: memeID
|
||||
}, "Počet hlasov k meme-u bol stiahnutý", "Nastala chyba pri sťahovaní počtu hlasov k meme-u", true);
|
||||
|
||||
let memeVotes = memeVoteResponse.NetVotes;
|
||||
let userVote = memeVoteResponse.UserVote;
|
||||
memeVoteCounterElement.innerText = memeVotes;
|
||||
memeVoteCounterElement.classList.remove("positive", "negative", "neutral");
|
||||
|
||||
if (0 < memeVotes) {
|
||||
memeVoteCounterElement.classList.add("positive");
|
||||
} else if (0 > memeVotes) {
|
||||
memeVoteCounterElement.classList.add("negative");
|
||||
} else {
|
||||
memeVoteCounterElement.classList.add("neutral");
|
||||
}
|
||||
|
||||
memeVoteUpvoteButtonElement.classList.remove('visual_hover');
|
||||
memeVoteDownvoteButtonElement.classList.remove('visual_hover');
|
||||
let memeUpvoteVariant = "line";
|
||||
let memeDownvoteVariant = "line";
|
||||
if (0 < userVote) {
|
||||
memeUpvoteVariant = "fill";
|
||||
memeVoteUpvoteButtonElement.classList.add('visual_hover');
|
||||
} else if (0 > userVote) {
|
||||
memeDownvoteVariant = "fill";
|
||||
memeVoteDownvoteButtonElement.classList.add('visual_hover');
|
||||
}
|
||||
|
||||
await setElementClasses(memeVoteUpvoteElement, [`ri-arrow-up-circle-${memeUpvoteVariant}`]);
|
||||
await setElementClasses(memeVoteDownvoteElement, [`ri-arrow-down-circle-${memeDownvoteVariant}`])
|
||||
|
||||
}
|
||||
|
||||
async function voteMeme(memeID, isUpvote) {
|
||||
let memeVoteUpvoteElement = document.getElementById(`meme_votes_upvote_${memeID}`);
|
||||
let memeVoteDownvoteElement = document.getElementById(`meme_votes_downvote_${memeID}`);
|
||||
let memeVoteDelete = false;
|
||||
if (isUpvote) {
|
||||
if (memeVoteUpvoteElement.classList.contains("ri-arrow-up-circle-fill")) {
|
||||
await deleteVoteMeme(memeID);
|
||||
memeVoteDelete = true;
|
||||
}
|
||||
} else {
|
||||
if (memeVoteDownvoteElement.classList.contains("ri-arrow-down-circle-fill")) {
|
||||
await deleteVoteMeme(memeID);
|
||||
memeVoteDelete = true;
|
||||
}
|
||||
}
|
||||
if (!memeVoteDelete) {
|
||||
await doAction("/meme", {
|
||||
action: "voteMeme",
|
||||
meme_id: memeID,
|
||||
is_upvote: isUpvote
|
||||
}, "Meme bol votovaný", "Nastala chyba pri votovaný", true);
|
||||
}
|
||||
await reloadMemeVotes(memeID);
|
||||
}
|
||||
|
||||
async function deleteVoteMeme(memeId) {
|
||||
await doAction("/meme", {
|
||||
action: "deleteVoteMeme",
|
||||
meme_id: memeId
|
||||
}, "Hlas na meme bol zmazaný", "Nastala chyba pri mazaní hlasu na meme", true);
|
||||
await reloadMemeVotes(memeId);
|
||||
}
|
||||
|
||||
async function surveySubmit() {
|
||||
const satisfaction = document.querySelector('input[name="satisfaction"]:checked');
|
||||
const functionality = document.querySelector('input[name="functionality"]:checked');
|
||||
const content = document.querySelector('input[name="content"]:checked');
|
||||
const comment = document.querySelector('textarea[name="comment"]');
|
||||
if (satisfaction && functionality && content && comment.value) {
|
||||
await doAction("/survey", {
|
||||
action: "surveySubmit",
|
||||
satisfaction: satisfaction.value,
|
||||
functionality: functionality.value,
|
||||
content: content.value,
|
||||
comment: comment.value
|
||||
}, "Zaznamenané",
|
||||
"Nastala chyba");
|
||||
satisfaction.checked = false;
|
||||
functionality.checked = false;
|
||||
content.checked = false;
|
||||
comment.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleRegister() {
|
||||
let loginForm = document.getElementById("sign_in_form");
|
||||
let registerForm = document.getElementById("sign_up_form");
|
||||
loginForm.classList.toggle('hidden');
|
||||
registerForm.classList.toggle('hidden');
|
||||
}
|
646
assets/style.css
646
assets/style.css
@ -1,504 +1,256 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
|
||||
|
||||
:root {
|
||||
--error: #ff3700;
|
||||
--pico-primary: #2a9dd6;
|
||||
--pico-primary-background: #1b1529;
|
||||
--pico-primary-hover: #2489bb;
|
||||
--pico-secondary: #d2d6e5;
|
||||
--pico-secondary-background: #1a1a1a;
|
||||
--dimmer: rgba(0, 0, 0, 0.6);
|
||||
--primary-bg: rgb(27, 21, 41);
|
||||
--secondary-bg: #1a1a1a;
|
||||
--third-bg: #383838;
|
||||
--primary-text: #d2d6e5;
|
||||
--error: rgb(255, 55, 0);
|
||||
--primary: #2a9dd6;
|
||||
--primary-hover: #2489bb;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-areas: "nav nav nav nav"
|
||||
"main main main main"
|
||||
"foot foot foot foot";
|
||||
grid-template-rows: min-content 1fr min-content;
|
||||
align-items: center;
|
||||
background: linear-gradient(127deg, var(--pico-secondary-background), var(--pico-primary-background)) no-repeat fixed;
|
||||
background: linear-gradient(127deg, var(--secondary-bg), var(--primary-bg)) no-repeat fixed;
|
||||
background-size: cover;
|
||||
flex-direction: column;
|
||||
font-family: \'Poppins\', sans-serif;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--primary-text);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
min-width: 100vw;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body > nav,
|
||||
body > footer {
|
||||
background-color: var(--dimmer);
|
||||
padding: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body > nav {
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 1.2rem 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: 0 20px 28px 0 rgba(0,0,0,0.2);
|
||||
-moz-box-shadow: 0 20px 28px 0 rgba(0,0,0,0.2);
|
||||
box-shadow: 0 20px 28px 0 rgba(0,0,0,0.2);
|
||||
height: 75px !important;
|
||||
position: relative;
|
||||
z-index: 500;
|
||||
grid-area: nav;
|
||||
box-shadow: 0 20px 28px 0 var(--dimmer);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
body > footer {
|
||||
grid-area: foot;
|
||||
box-shadow: 0 -20px 28px 0 var(--dimmer);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
body > main#page_container {
|
||||
grid-area: main;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body > main#page_container > main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: var(--pico-primary);
|
||||
opacity: 0.5;
|
||||
width: 30%;
|
||||
margin: var(--pico-typography-spacing-vertical) auto;
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2.5rem;
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
nav li {
|
||||
margin: unset;
|
||||
}
|
||||
|
||||
header ul li {
|
||||
list-style: circle;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
li a {
|
||||
padding-bottom: .45rem;
|
||||
position: relative;
|
||||
padding-bottom: 0.45rem;
|
||||
color: var(--primary-text);
|
||||
text-decoration: none;
|
||||
transition: all .3s ease;
|
||||
color: var(--pico-secondary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/*li a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
width: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: var(--primary);
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 15px;
|
||||
}*/
|
||||
|
||||
li a:hover::after {
|
||||
margin: 0 auto;
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
li.navpage_item {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
table>tbody,
|
||||
table>tbody>tr,
|
||||
table>tbody>tr>th,
|
||||
table>tbody>tr>td {
|
||||
border: 2px solid var(--pico-primary);
|
||||
border-collapse: collapse !important;
|
||||
.wrapper-40x {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table>tbody>tr>td>button {
|
||||
border: unset;
|
||||
border-radius: unset;
|
||||
border-collapse: unset;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
ul.navpage_list {
|
||||
gap: 10px;
|
||||
border: 4px solid var(--pico-primary-hover) !important;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: max-height .3s ease, border .325s ease !important;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
background: #00000066;
|
||||
}
|
||||
|
||||
.back {
|
||||
border-radius: 15px;
|
||||
padding: .35rem .65rem;
|
||||
text-decoration: none;
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.feature-list-ul ul {
|
||||
margin: 5px auto auto 20px;
|
||||
}
|
||||
|
||||
.navsite_item {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.navsite_item:hover .navpage_list {
|
||||
border: 4px solid var(--pico-primary-hover) !important;
|
||||
display: flex !important;
|
||||
max-height: unset;
|
||||
width: inherit;
|
||||
transition: max-height .3s ease, border .325s ease !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navsite_item:not(:hover) .navpage_list {
|
||||
border: 0 solid transparent;
|
||||
max-height: 0;
|
||||
transition: max-height .3s ease, border .325s ease !important;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
#navsite_list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3.25rem;
|
||||
text-align: center;
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
#toggle_button {
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 2rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
#statusMessageContainer {
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
z-index: 510;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*noinspection CssUnusedSymbol*/
|
||||
.status-message {
|
||||
background-color: #dff0d8;
|
||||
/* Success background color */
|
||||
border: 1px solid #3c763d;
|
||||
/* Success border color */
|
||||
color: #3c763d;
|
||||
/* Success text color */
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/*noinspection CssUnusedSymbol*/
|
||||
.status-message.failure {
|
||||
background-color: #f2dede;
|
||||
/* Failure background color */
|
||||
border: 1px solid #a94442;
|
||||
/* Failure border color */
|
||||
color: #a94442;
|
||||
/* Failure text color */
|
||||
}
|
||||
|
||||
.wrapper-40x .error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.wrapper-40x h1 {
|
||||
font-size: 10rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wrapper-40x .error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.wrapper-40x h3 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.wrapper-40x {
|
||||
.error-code {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.back {
|
||||
color: var(--primary-text);
|
||||
text-decoration: none;
|
||||
background-color: #2a9dd6;
|
||||
padding: 0.35rem 0.65rem;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.back:hover {
|
||||
transition: all 0.3s ease;
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
header h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
header a {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
header hr {
|
||||
border-color: var(--primary);
|
||||
opacity: 0.5;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.navsite_list{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3.25rem;
|
||||
}
|
||||
.navpage_list{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.navsite_item {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.navsite_item:hover .navpage_list{
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
|
||||
.navpage_list {
|
||||
background-color: var(--third-bg);
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
border: 0 solid transparent;
|
||||
transition: max-height 0.3s ease, border 0.325s ease;
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.navsite_item:hover .navpage_list {
|
||||
max-height: 200px;
|
||||
border: 4px solid var(--primary-hover);
|
||||
}
|
||||
|
||||
.navsite_item:not(:hover) .navpage_list {
|
||||
max-height: 0;
|
||||
border: 0 solid transparent;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
|
||||
li.navpage_item{
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
ul.navpage_list{
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
header ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
text-align: left;
|
||||
width: fit-content;
|
||||
}
|
||||
header ul li {
|
||||
list-style: circle;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.feature-list-ul {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.feature-list-ul ul {
|
||||
margin-top: 5px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#StatusMessage {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 10px;
|
||||
background-color: var(--third-bg);
|
||||
color: var(--primary-text);
|
||||
border-radius: 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
table.list-table > tbody, table.list-table > tbody > th, table.list-table > tbody > tr, table.list-table > tbody > tr > td {
|
||||
border: 2px solid var(--primary);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* <DASHBOARD STYLING> */
|
||||
/* ZAČÍNAJ VŠETKO S ".dashboard" */
|
||||
|
||||
|
||||
.dashboard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header a,
|
||||
.error-code {
|
||||
color: var(--pico-primary);
|
||||
}
|
||||
/* </DASHBOARD STYLING> */
|
||||
|
||||
input, textarea {
|
||||
border: 2px solid var(--pico-primary);
|
||||
}
|
||||
|
||||
input{
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
textarea{
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.form-container input {
|
||||
#loginForm input {
|
||||
border-radius: 50px;
|
||||
background: none;
|
||||
border: 2px solid var(--pico-primary);
|
||||
border: 2px solid var(--primary);
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
span#ye-span:hover + body{
|
||||
background: url('/assets/images/ye.jpg') repeat !important;
|
||||
background-size: 20% !important;
|
||||
}
|
||||
|
||||
|
||||
#articlecreate, #memecreate {
|
||||
border: 5px solid var(--pico-primary);
|
||||
z-index: 5;
|
||||
margin: auto;
|
||||
padding: 40px;
|
||||
background-color: var(--pico-primary-background);
|
||||
#register_Form input {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
#articlecreate > * {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#articlecreateopen {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#articlecreatecontainer, #memecreatecontainer{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 12vh;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 88vh;
|
||||
z-index: 4;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div#articleslist > article > div.articleinfo > *{
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/*noinspection CssUnusedSymbol*/
|
||||
div#articleslist > article > div.articleinfo{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
div#articleslist>article{
|
||||
border: 4px solid var(--pico-primary);
|
||||
}
|
||||
|
||||
.form-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meme_image {
|
||||
max-width: 500px;
|
||||
max-height: 300px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.meme_link {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.meme_info, .meme_topbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.meme, .meme_body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.neutral {
|
||||
color: var(--pico-color);
|
||||
}
|
||||
|
||||
.visual_hover {
|
||||
--pico-background-color: var(--pico-primary-hover-background);
|
||||
--pico-border-color: var(--pico-primary-hover-border);
|
||||
--pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));
|
||||
--pico-color: var(--pico-primary-inverse);
|
||||
}
|
||||
|
||||
.visual_hover.meme_upvote {
|
||||
--pico-background-color: #008000;
|
||||
--pico-border-color: unset;
|
||||
}
|
||||
|
||||
.visual_hover.meme_downvote {
|
||||
--pico-background-color: #ff0000;
|
||||
--pico-border-color: unset;
|
||||
}
|
||||
|
||||
#meme_gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
||||
grid-auto-rows: 1fr;
|
||||
gap: 20px
|
||||
}
|
||||
|
||||
@media (max-width: 1050px) {
|
||||
|
||||
|
||||
table .rozvrh {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.navsite_item .navpage_list {
|
||||
max-height: unset !important;
|
||||
}
|
||||
div#articleslist {
|
||||
width: 100vw !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
#toggle_button {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#navsite_list {
|
||||
display: none;
|
||||
position: fixed;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0 8px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#navsite_list li {
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navsite_item {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
ul.navpage_list {
|
||||
border: 4px solid var(--pico-primary-hover) !important;
|
||||
display: flex !important;
|
||||
max-height: 200px !important;
|
||||
width: inherit;
|
||||
box-sizing: content-box;
|
||||
transition-delay: .1s;
|
||||
position: unset !important;
|
||||
}
|
||||
.navsite_item:not(:hover) .navpage_list {
|
||||
transition-delay: .1s;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
/*noinspection CssUnusedSymbol*/
|
||||
#navsite_list.active {
|
||||
display: flex;
|
||||
-moz-box-shadow: 0 20px 28px 0 var(--dimmer);
|
||||
-webkit-box-shadow: 0 20px 28px 0 var(--dimmer);
|
||||
background-color: var(--pico-primary-background);
|
||||
box-shadow: 0 20px 28px 0 var(--dimmer);
|
||||
top: 100px;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: var(--dimmer);
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meme_image {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.navsite_link {
|
||||
width: 100%;
|
||||
}
|
||||
background: none;
|
||||
border: 2px solid var(--primary);
|
||||
width: 175px;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/meme.php";
|
||||
|
||||
function endpoint($endpoint_data): array
|
||||
{
|
||||
|
||||
return match ($endpoint_data["action"]) {
|
||||
"addMeme" => addMeme($endpoint_data['meme_title'], $endpoint_data['meme_text'], $endpoint_data['meme_image_id']),
|
||||
"getMemes" => getMemeGallery($endpoint_data['offset'], $endpoint_data['meme_author'], $endpoint_data['meme_id'], $endpoint_data['meme_keyword']),
|
||||
"deleteMeme" => deleteMeme($endpoint_data['meme_id']),
|
||||
"getMemeVotes" => getMemeVotes($endpoint_data['meme_id']),
|
||||
"deleteVoteMeme" => deleteVoteMeme($endpoint_data['meme_id']),
|
||||
"voteMeme" => voteMeme($endpoint_data['meme_id'], $endpoint_data['is_upvote']),
|
||||
default => ["Status" => "Fail", "Message" => "Invalid action"],
|
||||
};
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/newsarticle.php";
|
||||
|
||||
function endpoint($endpoint_data): array
|
||||
{
|
||||
|
||||
return match ($endpoint_data["action"]) {
|
||||
"getNewsArticles" => getNewsArticles(),
|
||||
"addNewsArticle" => addNewsArticle(
|
||||
$endpoint_data["title"],
|
||||
$endpoint_data["body"]
|
||||
),
|
||||
"addNewsComment" => addNewsComment(
|
||||
$endpoint_data["user_id"],
|
||||
$endpoint_data['news_article_id'],
|
||||
$endpoint_data["comment_text"],
|
||||
$endpoint_data["parent_id"]
|
||||
),
|
||||
default => ["Status" => "Fail", "message" => "Invalid action"],
|
||||
};
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/page.php";
|
||||
require_once "lib/navigation.php";
|
||||
|
||||
function endpoint($endpoint_data): array
|
||||
{
|
||||
return match ($endpoint_data["action"]) {
|
||||
"getNavigation" => getNavigationEndpoint(),
|
||||
"getPage" => getPageEndpoint($endpoint_data["page"], $endpoint_data["site"]),
|
||||
default => ["Status" => "Fail", "message" => "Invalid action"],
|
||||
};
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/survey.php";
|
||||
|
||||
function endpoint($endpoint_data): array
|
||||
{
|
||||
return match ($endpoint_data["action"]) {
|
||||
"surveySubmit" => submitSurvey($endpoint_data["satisfaction"], $endpoint_data["functionality"], $endpoint_data["content"], $endpoint_data["comment"]),
|
||||
default => ["Status" => "Fail", "message" => "Invalid action"],
|
||||
};
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/upload.php";
|
||||
|
||||
function endpoint($endpoint_data): array
|
||||
{
|
||||
|
||||
return match ($endpoint_data["action"]) {
|
||||
"getMyFiles" => listFiles(),
|
||||
"getAllFiles" => listFiles(false),
|
||||
"uploadFiles" => parseIncomingFiles(),
|
||||
"deleteFile" => deleteFile($endpoint_data['file_id']),
|
||||
"addToGroup" => addToGroup($endpoint_data['group_id'], $endpoint_data['file_id']),
|
||||
"myFileExists" => fileExists($endpoint_data['file_id']),
|
||||
"FileExists" => fileExists($endpoint_data['file_id'], false),
|
||||
default => ["Status" => "Fail", "message" => "Invalid action"],
|
||||
};
|
||||
}
|
61
index.php
61
index.php
@ -1,40 +1,35 @@
|
||||
<?php
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
// Include essential configuration and function libraries.
|
||||
require_once 'secrets/config.php'; // Load sensitive configuration such as database credentials.
|
||||
require_once 'lib/config.php'; // Load general site configuration settings.
|
||||
require_once 'lib/navigation.php'; // Include functions related to navigation generation.
|
||||
require_once 'lib/router.php'; // Include routing functionality to manage URL routing.
|
||||
require_once 'lib/page.php'; // Functions related to page content generation and management.
|
||||
require_once 'lib/endpoint.php'; // Functions for handling API endpoints.
|
||||
require_once 'lib/account.php'; // Include user account management functionality.
|
||||
require_once 'secrets/config.php';
|
||||
require_once 'lib/config.php';
|
||||
require_once 'lib/navigation.php';
|
||||
require_once 'lib/router.php';
|
||||
require_once 'lib/page.php';
|
||||
require_once 'lib/endpoint.php';
|
||||
require_once 'lib/account.php';
|
||||
|
||||
// Load configuration for the router from the configuration files.
|
||||
$routerConfig = loadRouterConfig();
|
||||
$routerConfig = array();
|
||||
$routerRequest = array();
|
||||
|
||||
// Initialize the router to parse the request URI and determine the requested site/page.
|
||||
$routerRequest = initRouter();
|
||||
loadRouterConfig();
|
||||
$canRender = initRouter();
|
||||
|
||||
// Start or resume a session to manage user sessions across requests.
|
||||
session_start();
|
||||
if ($canRender) {
|
||||
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
|
||||
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
|
||||
session_set_cookie_params(0, '/', "." . $routerRequest["domain"] . "." . $routerRequest["tld"], true, true);
|
||||
session_start();
|
||||
|
||||
// Set default session data if the user is not logged in.
|
||||
if (!isLoggedIn()) {
|
||||
setDefaultSessionData();
|
||||
}
|
||||
if (!isLoggedIn()) {
|
||||
setDefaultSessionData();
|
||||
}
|
||||
|
||||
// Handle requests for the sitemap.
|
||||
if ($routerRequest["site_name"] == "sitemap.xml") {
|
||||
require "lib/sitemap.php"; // Include sitemap generation functions.
|
||||
echo generateSitemap(); // Generate and output the sitemap XML.
|
||||
exit(); // Stop script execution after sitemap generation.
|
||||
}
|
||||
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
|
||||
if ($routerRequest["type"] == "api") {
|
||||
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
|
||||
echo getEndpoint($routerRequest["page_name"]);
|
||||
|
||||
// Handle API type requests by fetching and outputting the endpoint response.
|
||||
if ($routerRequest["type"] == "api") {
|
||||
echo getEndpoint($routerRequest["site_name"]);
|
||||
}
|
||||
// Handle page type requests by fetching and rendering the page content.
|
||||
elseif ($routerRequest["type"] == "page") {
|
||||
echo getPage($routerRequest["site_name"], $routerRequest["page_name"]);
|
||||
}
|
||||
} /** @noinspection PhpArrayIsAlwaysEmptyInspection */ elseif ($routerRequest["type"] == "page") {
|
||||
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
|
||||
echo getPage($routerRequest["page_name"]);
|
||||
}
|
||||
}
|
243
lib/account.php
243
lib/account.php
@ -1,95 +1,52 @@
|
||||
<?php
|
||||
|
||||
use Random\RandomException;
|
||||
/**
|
||||
* Checks if the current session represents a logged-in user.
|
||||
*
|
||||
* @global array $routerConfig Global configuration array containing permission thresholds.
|
||||
* @return bool Returns true if the user is logged in and meets the minimum privilege level; otherwise, false.
|
||||
*/
|
||||
|
||||
function isLoggedIn(): bool
|
||||
{
|
||||
global $routerConfig;
|
||||
return $_SESSION["ID"] > 0 && !empty($_SESSION["email"]) && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["logged_in_default"];
|
||||
return $_SESSION["ID"] > 0 && !empty($_SESSION["email"]) && $_SESSION["privilege_level"] >= $routerConfig["logged_in_default_permission_level"];
|
||||
}
|
||||
/**
|
||||
* Checks if the logged-in user is verified.
|
||||
*
|
||||
* @global array $routerConfig Global configuration array containing permission thresholds.
|
||||
* @return bool Returns true if the user is logged in and verified; otherwise, false.
|
||||
*/
|
||||
function isVerified(): bool
|
||||
{
|
||||
global $routerConfig;
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["verified"];
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["verified_permission_level"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the logged-in user is trustworthy.
|
||||
*
|
||||
* @global array $routerConfig Global configuration array containing permission thresholds.
|
||||
* @return bool Returns true if the user is logged in and considered trustworthy; otherwise, false.
|
||||
*/
|
||||
function isTrustWorthy(): bool
|
||||
{
|
||||
global $routerConfig;
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["trustworthy"];
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["trustworthy_permission_level"];
|
||||
}
|
||||
/**
|
||||
* Checks if the logged-in user is a moderator.
|
||||
*
|
||||
* @global array $routerConfig Global configuration array containing permission thresholds.
|
||||
* @return bool Returns true if the user is logged in and a moderator; otherwise, false.
|
||||
*/
|
||||
|
||||
function isModerator(): bool
|
||||
{
|
||||
global $routerConfig;
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["moderator"];
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["moderator_permission_level"];
|
||||
}
|
||||
/**
|
||||
* Checks if the logged-in user is a user admin.
|
||||
*
|
||||
* @global array $routerConfig Global configuration array containing permission thresholds.
|
||||
* @return bool Returns true if the user is logged in and a user admin; otherwise, false.
|
||||
*/
|
||||
|
||||
function isUserAdmin(): bool
|
||||
{
|
||||
global $routerConfig;
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["user_admin"];
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["user_admin_permission_level"];
|
||||
}
|
||||
/**
|
||||
* Checks if the logged-in user is an admin.
|
||||
*
|
||||
* @global array $routerConfig Global configuration array containing permission thresholds.
|
||||
* @return bool Returns true if the user is logged in and an admin; otherwise, false.
|
||||
*/
|
||||
|
||||
function isAdmin(): bool
|
||||
{
|
||||
global $routerConfig;
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["admin"];
|
||||
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["admin_permission_level"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a secure token for account activation or other purposes using cryptographic methods.
|
||||
*
|
||||
* @return string|null Returns a hexadecimal token or null in case of an error.
|
||||
*/
|
||||
function generateActivationToken(): ?string
|
||||
|
||||
function generateActivationToken(): string
|
||||
{
|
||||
try {
|
||||
return bin2hex(random_bytes(16));
|
||||
} catch (RandomException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if an email address is available for registration.
|
||||
*
|
||||
* @param string $email The email address to check.
|
||||
* @return bool Returns true if the email is not already registered; otherwise, false.
|
||||
*@global mysqli $mysqli Global mysqli object for database access.
|
||||
*/
|
||||
function isEmailAvailable(string $email): bool
|
||||
|
||||
function isEmailAvailable($email): bool
|
||||
{
|
||||
global $mysqli;
|
||||
$stmt = $mysqli->prepare("SELECT COUNT(*) FROM Users WHERE Email = ?");
|
||||
@ -102,12 +59,7 @@ function isEmailAvailable(string $email): bool
|
||||
|
||||
return $count === 0;
|
||||
}
|
||||
/**
|
||||
* Sets default session data typically used for a logged-out user(includes users that have just visited the page).
|
||||
*
|
||||
* @global array $routerConfig Global configuration array used for setting initial privilege levels.
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function setDefaultSessionData(): void
|
||||
{
|
||||
global $routerConfig;
|
||||
@ -117,17 +69,10 @@ function setDefaultSessionData(): void
|
||||
$_SESSION["nickname"] = "";
|
||||
$_SESSION["email"] = "";
|
||||
$_SESSION["minecraft_nickname"] = "";
|
||||
$_SESSION["privilege_level"] = $routerConfig["permissions"]["logged_out"];
|
||||
$_SESSION["privilege_level"] = $routerConfig["logged_out_permission_level"];
|
||||
}
|
||||
/**
|
||||
* Verifies if the provided password matches the stored hash for the user.
|
||||
*
|
||||
* @param int $userID The user ID whose password is to be verified.
|
||||
* @param string $password The password to verify.
|
||||
* @return bool Returns true if the password matches the stored hash; otherwise, false.
|
||||
*@global mysqli $mysqli Global mysqli object for database access.
|
||||
*/
|
||||
function verifyPassword(int $userID, string $password): bool
|
||||
|
||||
function verifyPassword($userID, $password): bool
|
||||
{
|
||||
global $mysqli;
|
||||
$stmt = $mysqli->prepare("SELECT PasswordHash FROM Users WHERE ID = ?");
|
||||
@ -140,22 +85,18 @@ function verifyPassword(int $userID, string $password): bool
|
||||
|
||||
return !empty($password_hash) && !empty($password) && password_verify($password, $password_hash);
|
||||
}
|
||||
/**
|
||||
* Updates session data from the database for the logged-in user.
|
||||
*
|
||||
* @global mysqli $mysqli Global mysqli object for database access.
|
||||
* @return void
|
||||
*/
|
||||
function UpdateSession(): void
|
||||
{
|
||||
|
||||
function UpdateSession(){
|
||||
global $mysqli;
|
||||
$stmt = $mysqli->prepare("SELECT FirstName, LastName, Nickname, Email, MinecraftNick, PrivilegeLevel, LastLoginAt, LoginCount, ClassID, FavoriteColor FROM Users WHERE ID = ? AND isActivated = 1");
|
||||
$stmt->bind_param("i", $_SESSION["ID"]);
|
||||
$stmt->execute();
|
||||
|
||||
$uid = 0;
|
||||
$first_name = "";
|
||||
$last_name = "";
|
||||
$nickname = "";
|
||||
$password_hash = "";
|
||||
$email = "";
|
||||
$minecraft_nickname = "";
|
||||
$privilege_level = 0;
|
||||
@ -166,6 +107,7 @@ function UpdateSession(): void
|
||||
$stmt->bind_result($first_name, $last_name, $nickname, $email, $minecraft_nickname, $privilege_level, $lastLoginAt, $loginCount, $class_id, $favorite_color);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$_SESSION["first_name"] = $first_name;
|
||||
$_SESSION["last_name"] = $last_name;
|
||||
$_SESSION["nickname"] = $nickname;
|
||||
@ -178,17 +120,10 @@ function UpdateSession(): void
|
||||
$_SESSION["favorite_color"] = $favorite_color;
|
||||
|
||||
}
|
||||
/**
|
||||
* Attempts to log in a user with the given credentials.
|
||||
*
|
||||
* @param string $email The user's email address.
|
||||
* @param string $password The user's password.
|
||||
* @global mysqli $mysqli Global database connection object.
|
||||
* @return array An array containing the status of the login attempt ('Success' or 'Fail').
|
||||
*/
|
||||
function doLogin(string $email, string $password): array
|
||||
|
||||
function doLogin($email, $password): array
|
||||
{
|
||||
global $mysqli;
|
||||
global $mysqli, $routerConfig;
|
||||
$found = false;
|
||||
if (!empty($email) && !empty($password)) {
|
||||
$stmt = $mysqli->prepare("SELECT ID, PasswordHash FROM Users WHERE Email = ? AND isActivated = 1");
|
||||
@ -216,12 +151,7 @@ function doLogin(string $email, string $password): array
|
||||
}
|
||||
return $found ? ["Status" => "Success"] : ["Status" => "Fail"];
|
||||
}
|
||||
/**
|
||||
* Logs out the current user by resetting session data.
|
||||
* Fails when the user wasn't logged in
|
||||
*
|
||||
* @return array An array with the logout status ('Success' if logged out, 'Fail' otherwise).
|
||||
*/
|
||||
|
||||
function doLogout(): array
|
||||
{
|
||||
if(isLoggedIn()){
|
||||
@ -231,19 +161,8 @@ function doLogout(): array
|
||||
return ["Status" => "Fail"];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Registers a new user with provided personal details and activation token.
|
||||
*
|
||||
* @param string $firstname The user's first name.
|
||||
* @param string $lastname The user's last name.
|
||||
* @param string $email The user's email.
|
||||
* @param string $password The user's password.
|
||||
* @param string $activation_token The activation token to verify the registration.
|
||||
* @global mysqli $mysqli Global database connection object.
|
||||
* @global array $routerConfig Global configuration settings.
|
||||
* @return array An array with the registration status ('Success' or 'Fail').
|
||||
*/
|
||||
function doRegister(string $firstname, string $lastname, string $email, string $password, string $activation_token): array
|
||||
|
||||
function doRegister($firstname, $lastname, $email, $password, $activation_token): array
|
||||
{
|
||||
global $mysqli, $routerConfig;
|
||||
$status = ["Status" => "Fail"];
|
||||
@ -252,7 +171,7 @@ function doRegister(string $firstname, string $lastname, string $email, string $
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $mysqli->prepare("UPDATE Users SET FirstName=?, LastName=?, Email=?, PasswordHash=?, PrivilegeLevel=?, isActivated=1, ActivationToken='', RegisteredAt=NOW() WHERE ActivationToken = ?");
|
||||
$privilege_level = $routerConfig["permissions"]["logged_in_default"];
|
||||
$privilege_level = $routerConfig["logged_in_default_permission_level"];
|
||||
|
||||
/** @noinspection SpellCheckingInspection */
|
||||
$stmt->bind_param("ssssis", $firstname, $lastname, $email, $passwordHash, $privilege_level, $activation_token);
|
||||
@ -269,15 +188,8 @@ function doRegister(string $firstname, string $lastname, string $email, string $
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the user's password after verifying the old password.
|
||||
*
|
||||
* @param string $oldPassword The current password for verification.
|
||||
* @param string $newPassword The new password to be set.
|
||||
* @return array An array indicating whether the password change was successful ('Success' or 'Fail').
|
||||
*@global mysqli $mysqli Global database connection object.
|
||||
*/
|
||||
function changePassword(string $oldPassword, string $newPassword): array
|
||||
|
||||
function changePassword($oldPassword, $newPassword): array
|
||||
{
|
||||
global $mysqli;
|
||||
$status = ["Status" => "Fail"];
|
||||
@ -296,34 +208,23 @@ function changePassword(string $oldPassword, string $newPassword): array
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates user profile information in the database.
|
||||
*
|
||||
* @param string $firstName The new first name.
|
||||
* @param string $lastName The new last name.
|
||||
* @param string $nickname The new nickname.
|
||||
* @param string $minecraft_nickname The new Minecraft nickname.
|
||||
* @return array Status of the profile update ('Success' or 'Fail').
|
||||
*@global mysqli $mysqli Global database connection object.
|
||||
*/
|
||||
function updateUserProfile(string $firstName, string $lastName, string $nickname, string $minecraft_nickname): array
|
||||
// Function to update user profile
|
||||
function updateUserProfile($firstName, $lastName, $nickname, $minecraft_nickname): array
|
||||
{
|
||||
global $mysqli;
|
||||
$status = ["Status" => "Fail"];
|
||||
|
||||
if (isLoggedIn() && !empty($firstName) && !empty($lastName) && !empty($nickname) && !empty($minecraft_nickname)) {
|
||||
$userID = $_SESSION["ID"];
|
||||
|
||||
$stmt = $mysqli->prepare("UPDATE Users SET FirstName = ?, LastName = ?, Nickname = ?, MinecraftNick = ? WHERE ID = ?");
|
||||
/** @noinspection SpellCheckingInspection */
|
||||
$stmt->bind_param("ssssi", $firstName, $lastName, $nickname, $minecraft_nickname, $_SESSION["ID"]);
|
||||
$stmt->bind_param("ssssi", $firstName, $lastName, $nickname, $minecraft_nickname, $userID);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->affected_rows > 0) {
|
||||
$status["Status"] = "Success";
|
||||
}
|
||||
else {
|
||||
$status["Status"] = "$firstName $lastName $nickname $minecraft_nickname";
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
}
|
||||
@ -331,14 +232,8 @@ function updateUserProfile(string $firstName, string $lastName, string $nickname
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the email address of the logged-in user after validation.
|
||||
*
|
||||
* @param string $email The new email address to update.
|
||||
* @return array Status of the email update ('Success' or 'Fail').
|
||||
*@global mysqli $mysqli Global database connection object.
|
||||
*/
|
||||
function updateUserEmail(string $email): array
|
||||
// Function to update user email
|
||||
function updateUserEmail($email): array
|
||||
{
|
||||
global $mysqli;
|
||||
$status = ["Status" => "Fail"];
|
||||
@ -381,11 +276,7 @@ function updateUserEmail(string $email): array
|
||||
|
||||
return $status;
|
||||
}
|
||||
/**
|
||||
* Retrieves and updates the current session with user information from the database.
|
||||
*
|
||||
* @return array Contains user information and status if the user is logged in.
|
||||
*/
|
||||
|
||||
function getUserInfo(): array
|
||||
{
|
||||
$output = ["Status" => "Fail"];
|
||||
@ -421,16 +312,10 @@ function getUserInfo(): array
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a specified number of activation codes for user registration and adds them to the database.
|
||||
*
|
||||
* @param int $count Number of activation codes to generate.
|
||||
* @return array An array containing the generated codes and status ('Success' or 'Fail').
|
||||
*@global mysqli $mysqli Global database connection object.
|
||||
*/
|
||||
function addActivationCodes(int $count): array
|
||||
|
||||
function addActivationCodes($count): array
|
||||
{
|
||||
global $mysqli;
|
||||
global $mysqli, $routerConfig;
|
||||
$activationCodes = [];
|
||||
|
||||
$output = ["Status" => "Fail"]; // Default Status is "Fail"
|
||||
@ -459,20 +344,15 @@ function addActivationCodes(int $count): array
|
||||
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* Lists all registered users, available only to user admins.
|
||||
*
|
||||
* @global mysqli $mysqli Global database connection object.
|
||||
* @return array An array containing user data and status.
|
||||
*/
|
||||
|
||||
function listUsers(): array
|
||||
{
|
||||
global $mysqli;
|
||||
global $mysqli, $routerConfig;
|
||||
$output = ["Status" => "Fail"]; // Default Status is "Fail"
|
||||
|
||||
if (isUserAdmin()) {
|
||||
$users = [];
|
||||
$result = $mysqli->query("SELECT ID, FirstName, LastName, Nickname, Email, MinecraftNick, PrivilegeLevel, CreatedAt, RegisteredAt, LastLoginAt, LoginCount, CreatedBy FROM Users WHERE isActivated = 1");
|
||||
$result = $mysqli->query("SELECT ID, FirstName, LastName, Nickname, Email, MinecraftNick, PrivilegeLevel, CreatedAt, RegisteredAt, LastLoginAt, LoginCount, CreatedBy FROM Users");
|
||||
|
||||
// Check if the query executed Successfully
|
||||
if ($result) {
|
||||
@ -486,15 +366,10 @@ function listUsers(): array
|
||||
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* Lists activation codes available for assigning to new users, available only for user admins.
|
||||
*
|
||||
* @global mysqli $mysqli Global database connection object.
|
||||
* @return array An array containing activation codes and status.
|
||||
*/
|
||||
|
||||
function listActivationCodes(): array
|
||||
{
|
||||
global $mysqli;
|
||||
global $mysqli, $routerConfig;
|
||||
$output = ["Status" => "Fail"]; // Default Status is "Fail"
|
||||
|
||||
if (isUserAdmin()) {
|
||||
@ -536,16 +411,10 @@ function listActivationCodes(): array
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user by their ID, available only to user admins.
|
||||
*
|
||||
* @param int $userID The ID of the user to delete.
|
||||
* @return array Status of the delete operation ('Success' or 'Fail').
|
||||
*@global mysqli $mysqli Global database connection object.
|
||||
*/
|
||||
function deleteUser(int $userID): array
|
||||
|
||||
function deleteUser($userID): array
|
||||
{
|
||||
global $mysqli;
|
||||
global $mysqli, $routerConfig;
|
||||
$status = ["Status" => "Fail"];
|
||||
if (!empty($userID) && isUserAdmin()) {
|
||||
$stmt = $mysqli->prepare("DELETE FROM Users WHERE ID = ?");
|
||||
@ -558,16 +427,10 @@ function deleteUser(int $userID): array
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
/**
|
||||
* Deletes an activation code, available only to user admins.
|
||||
*
|
||||
* @param string $activationCode The activation code to delete.
|
||||
* @return array Status of the delete operation ('Success' or 'Fail').
|
||||
*@global mysqli $mysqli Global database connection object.
|
||||
*/
|
||||
function deleteActivationCode(string $activationCode): array
|
||||
|
||||
function deleteActivationCode($activationCode): array
|
||||
{
|
||||
global $mysqli;
|
||||
global $mysqli, $routerConfig;
|
||||
$status = ["Status" => "Fail"];
|
||||
if (!empty($activationCode) && isUserAdmin()) {
|
||||
$stmt = $mysqli->prepare("DELETE FROM Users WHERE ActivationToken = ?");
|
||||
|
@ -1,68 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Loads and returns the configuration settings for the router.
|
||||
*
|
||||
* This configuration includes various paths, default settings, security levels, SEO settings,
|
||||
* and other parameters essential for the operation of the router and the website's page management.
|
||||
* The configuration array is structured to provide easy access to paths, protocols, permissions,
|
||||
* and other critical settings that define how the router handles requests and serves content.
|
||||
*
|
||||
* @return array Returns an associative array containing all router configuration settings, such as:
|
||||
* - 'inlining': Boolean value determining if CSS/JS should be inlined.
|
||||
* - 'domain': The primary domain name of the website.
|
||||
* - 'tld': Top-level domain for the website.
|
||||
* - 'default_page': Default page to load if no specific page is requested.
|
||||
* - 'default_site': Default site to load if no specific site is requested.
|
||||
* - 'template_dir': Directory path where templates are stored.
|
||||
* - 'endpoint_dir': Directory path for endpoint scripts.
|
||||
* - 'page_dir': Directory path where site pages are stored.
|
||||
* - 'protocol': Protocol to be used (e.g., 'https://').
|
||||
* - 'site_prefix': Prefix for the site title.
|
||||
* - 'permissions': Associative array of user permissions by role.
|
||||
* - 'page': Default settings for pages including secret status and permissions.
|
||||
* - 'newsarticle': Default permissions for news articles.
|
||||
* - 'seo': Search engine optimization settings like author, description, and keywords.
|
||||
*/
|
||||
function loadRouterConfig(): array
|
||||
function loadRouterConfig(): void
|
||||
{
|
||||
global $routerConfig;
|
||||
|
||||
$routerConfig["default_page"] = "index";
|
||||
|
||||
return [
|
||||
'inlining' => false,
|
||||
'domain' => 'adlerka',
|
||||
'tld' => 'top',
|
||||
'default_page' => 'index',
|
||||
'default_site' => 'home',
|
||||
'template_dir' => 'templates/',
|
||||
'endpoint_dir' => 'endpoints/',
|
||||
'page_dir' => 'pages/',
|
||||
'protocol' => 'https://',
|
||||
'site_prefix' => 'Adlerka',
|
||||
'permissions' => [
|
||||
'logged_out' => 1,
|
||||
'logged_in_default' => 2,
|
||||
'verified' => 3,
|
||||
'trustworthy' => 4,
|
||||
'moderator' => 5,
|
||||
'user_admin' => 254,
|
||||
'admin' => 255,
|
||||
],
|
||||
'page' => [
|
||||
'default_secret' => 1,
|
||||
'default_permissions' => 255,
|
||||
$routerConfig["default_site"] = "home";
|
||||
|
||||
],
|
||||
'newsarticle' => [
|
||||
'default_permissions' => 255,
|
||||
],
|
||||
'meme' => [
|
||||
'per_page' => 10
|
||||
],
|
||||
'seo' => [
|
||||
'author' => 'Tím AdlerkaTop',
|
||||
'description' => 'Toto je neoficiánla študentská stránka pre Adlerku, kde môžete nájsť plno zaujímavostí.',
|
||||
'keywords' => 'adlerka, alderka, studenti, studentska stranka, web, dev, webdev, web dev, skola, zabava',
|
||||
'generator' => 'TurboRoute',
|
||||
'robots' => 'follow, index, max-snippet:-1, max-video-preview:-1, max-image-preview:large'
|
||||
]
|
||||
];
|
||||
$routerConfig["template_dir"] = "templates/";
|
||||
|
||||
$routerConfig["endpoint_dir"] = "endpoints/";
|
||||
|
||||
$routerConfig["page_dir"] = "pages/";
|
||||
|
||||
$routerConfig["protocol"] = "https://";
|
||||
|
||||
$routerConfig["logged_out_permission_level"] = 1;
|
||||
|
||||
$routerConfig["logged_in_default_permission_level"] = 2;
|
||||
|
||||
$routerConfig["verified_permission_level"] = 3;
|
||||
|
||||
$routerConfig["trustworthy_permission_level"] = 4;
|
||||
|
||||
$routerConfig["moderator_permission_level"] = 5;
|
||||
|
||||
$routerConfig["user_admin_permission_level"] = 254;
|
||||
|
||||
$routerConfig["admin_permission_level"] = 255;
|
||||
|
||||
$routerConfig["default_page_permission_level"] = 255;
|
||||
|
||||
$routerConfig["default_page_secret"] = 1;
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Generates dynamic CSS styling based on user preferences stored in the session.
|
||||
* Specifically, it creates a CSS rule for the user's favorite color if it's specified in their session data.
|
||||
*
|
||||
* @return string Returns a string containing a `<style>` tag with custom CSS if a favorite color is set
|
||||
* and the user is logged in. Returns an empty string if no conditions are met.
|
||||
*/
|
||||
function doDynamicStyling() :string
|
||||
{
|
||||
$dynamic_style = "";
|
||||
if(isLoggedIn() && !empty($_SESSION["favorite_color"]) && is_int($_SESSION["favorite_color"]) && $_SESSION["favorite_color"] <= 4294967295){
|
||||
$dynamic_style = "<style>";
|
||||
$color = dechex($_SESSION["favorite_color"]);
|
||||
$dynamic_style .= "--root{ --favorite-color: #$color;";
|
||||
$dynamic_style .= "</style>";
|
||||
}
|
||||
return $dynamic_style;
|
||||
|
||||
}
|
@ -1,16 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Executes an endpoint script and returns the results.
|
||||
*
|
||||
* This function includes an endpoint PHP file that defines a function named `endpoint` which
|
||||
* is expected to accept an array parameter and return an array result.
|
||||
* It simply scopes an external file into a function to prevent variable conflicts.
|
||||
*
|
||||
* @param string $endpoint_file The path to the endpoint PHP file.
|
||||
* @return array|null Returns the result of the endpoint function if successful, or null if the
|
||||
* endpoint function or file does not behave as expected.
|
||||
*/
|
||||
function runEndpoint(string $endpoint_file): ?array
|
||||
|
||||
function runEndpoint($endpoint_file): ?array
|
||||
{
|
||||
|
||||
$endpoint_data = $_POST;
|
||||
@ -19,20 +9,8 @@ function runEndpoint(string $endpoint_file): ?array
|
||||
return endpoint($endpoint_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and processes the output of a specified endpoint.
|
||||
*
|
||||
* This function determines the appropriate endpoint PHP file based on the provided endpoint name,
|
||||
* executes the endpoint, and returns its results as a JSON-encoded string. It handles and
|
||||
* translates different return types into a JSON format and manages HTTP response codes based on
|
||||
* success or failure of the endpoint execution.
|
||||
*
|
||||
* @param string $endpoint_name The name of the endpoint, which is used to construct the file path to the endpoint script.
|
||||
* @return string A JSON-encoded string representing the result of the endpoint, including a status indicator and any relevant data or error messages.
|
||||
*@global array $routerRequest Current request data that might influence the endpoint processing.
|
||||
* @global array $routerConfig Global configuration that contains paths and settings.
|
||||
*/
|
||||
function getEndpoint(string $endpoint_name): string
|
||||
|
||||
function getEndpoint($endpoint_name): string
|
||||
{
|
||||
$output = array();
|
||||
$output["Status"] = "Fail";
|
||||
@ -40,28 +18,28 @@ function getEndpoint(string $endpoint_name): string
|
||||
global $routerRequest;
|
||||
|
||||
if(!$endpoint_name){
|
||||
$endpoint_name = $routerRequest["site_name"];
|
||||
$endpoint_name = $routerRequest["page_name"];
|
||||
}
|
||||
|
||||
$endpoint_file = $routerConfig["endpoint_dir"] . $endpoint_name . ".php";
|
||||
|
||||
if (file_exists($endpoint_file)){
|
||||
$output_tmp = runEndpoint($endpoint_file);
|
||||
$output["Endpoint"] = $endpoint_name;
|
||||
$type = gettype($output_tmp);
|
||||
switch ($type) {
|
||||
case 'array':
|
||||
$output = $output_tmp;
|
||||
break;
|
||||
default:
|
||||
$output['Status'] = 'Fail';
|
||||
$output["Error"] = "Endpoint error";
|
||||
$output["Type"] = $type;
|
||||
http_response_code(500);
|
||||
}
|
||||
if($routerRequest["isToApex"]){
|
||||
$subdomain_part = "";
|
||||
}
|
||||
else{
|
||||
$output["Error"] = "Not found";
|
||||
$subdomain_part = $routerRequest["subdomain"] . "/";
|
||||
}
|
||||
|
||||
$endpoint_file = $routerConfig["endpoint_dir"] . $subdomain_part . $endpoint_name . ".php";
|
||||
|
||||
$endpoint_file_global = $routerConfig["endpoint_dir"] . "global/" . $endpoint_name . ".php";
|
||||
|
||||
if (file_exists($endpoint_file_global)){
|
||||
$output = runEndpoint($endpoint_file_global);
|
||||
}
|
||||
elseif (file_exists($endpoint_file)){
|
||||
$output = runEndpoint($endpoint_file);
|
||||
}
|
||||
else{
|
||||
$output["error"] = "Not found";
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* Processes an HTML string to inline all linked stylesheets by replacing <link> tags
|
||||
* with corresponding <style> tags containing the CSS content.
|
||||
* Might be broken, currently disabled in the config
|
||||
*
|
||||
* @param string $inputString The HTML content containing <link> tags to stylesheets.
|
||||
* @return string The modified HTML content with stylesheets inlined within <style> tags.
|
||||
*/
|
||||
function inlineLocalStylesFromHref(string $inputString): string
|
||||
{
|
||||
|
||||
function inlineLocalStylesFromHref($inputString) {
|
||||
$pattern = '/<link[^>]*?\srel=["\']?stylesheet["\'].*?\shref=["\']?\/(.*?)["\'][^>]*?>/i';
|
||||
|
||||
return preg_replace_callback($pattern, function($match) {
|
||||
$outputString = preg_replace_callback($pattern, function($match) {
|
||||
$href = $match[1];
|
||||
$cssFilePath = $_SERVER['DOCUMENT_ROOT'] . '/' . $href;
|
||||
$cssContent = file_get_contents($cssFilePath);
|
||||
@ -34,39 +26,28 @@ function inlineLocalStylesFromHref(string $inputString): string
|
||||
// Minify the CSS content
|
||||
$cssContent = minifyCss($cssContent);
|
||||
|
||||
return "<style>$cssContent</style>";
|
||||
return "<style>{$cssContent}</style>";
|
||||
}, $inputString);
|
||||
|
||||
return $outputString;
|
||||
}
|
||||
/**
|
||||
* Processes an HTML string to inline all external JavaScript files by replacing <script src="..."> tags
|
||||
* with <script> tags containing the JavaScript content.
|
||||
* Might be broken, currently disabled in the config
|
||||
*
|
||||
* @param string $inputString The HTML content containing <script src=""> tags.
|
||||
* @return string The modified HTML content with external scripts inlined within <script> tags.
|
||||
*/
|
||||
function inlineScriptFromSrc(string $inputString): string
|
||||
{
|
||||
|
||||
function inlineScriptFromSrc($inputString) {
|
||||
$pattern = '/<script.*?src=["\']\/(.*?)["\'].*?>\s*<\/script>/i';
|
||||
|
||||
return preg_replace_callback($pattern, function($match) {
|
||||
$outputString = preg_replace_callback($pattern, function($match) {
|
||||
$src = $match[1];
|
||||
$jsContent = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/' . $src);
|
||||
|
||||
// Minify the JavaScript content
|
||||
$jsContent = minifyJs($jsContent);
|
||||
return "<script>$jsContent</script>";
|
||||
return "<script>{$jsContent}</script>";
|
||||
}, $inputString);
|
||||
|
||||
return $outputString;
|
||||
}
|
||||
/**
|
||||
* Minifies CSS content by removing comments, unnecessary whitespaces, semicolons, and optimizing other aspects of the stylesheet.
|
||||
* Might be broken, currently disabled in the config
|
||||
*
|
||||
* @param string $css The original CSS content.
|
||||
* @return string The minified CSS content.
|
||||
*/
|
||||
function minifyCss(string $css): string
|
||||
{
|
||||
|
||||
function minifyCss($css) {
|
||||
// Remove comments
|
||||
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
|
||||
|
||||
@ -84,15 +65,8 @@ function minifyCss(string $css): string
|
||||
|
||||
return trim($css);
|
||||
}
|
||||
/**
|
||||
* Minifies JavaScript content by removing comments, unnecessary whitespaces, and optimizing spaces around operators.
|
||||
* Might be broken, currently disabled in the config
|
||||
*
|
||||
* @param string $js The original JavaScript content.
|
||||
* @return string The minified JavaScript content.
|
||||
*/
|
||||
function minifyJs(string $js): string
|
||||
{
|
||||
|
||||
function minifyJs($js) {
|
||||
|
||||
// Remove newlines and tabs
|
||||
$js = str_replace("\t", '', $js);
|
||||
|
316
lib/meme.php
316
lib/meme.php
@ -1,316 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/upload.php";
|
||||
require_once "lib/account.php";
|
||||
/**
|
||||
* Adds a meme to the database with associated image and text content.
|
||||
*
|
||||
* @param string $title The title of the meme.
|
||||
* @param string $memeText The text content of the meme.
|
||||
* @param int $imageID The ID of the image associated with the meme.
|
||||
* @return array Returns an associative array with the operation status and a message.
|
||||
* @global mysqli $mysqli The database connection object.
|
||||
*/
|
||||
function addMeme(string $title, string $memeText, int $imageID): array
|
||||
{
|
||||
global $mysqli;
|
||||
$output = ["Status" => "Fail"];
|
||||
if (isLoggedIn() && fileExists($imageID, false) && !empty($title) && !empty($memeText) && !empty($imageID) && $imageID > 0) {
|
||||
$stmtMemeAdd = $mysqli->prepare('INSERT INTO Memes (AuthorID, Title, TextContent, FileID) VALUES (?, ?, ?, ?)');
|
||||
$stmtMemeAdd->bind_param('issi', $_SESSION['ID'], htmlspecialchars($title), htmlspecialchars($memeText), $imageID);
|
||||
if ($stmtMemeAdd->execute() && $stmtMemeAdd->affected_rows > 0) {
|
||||
$output["Status"] = "Success";
|
||||
$output["Meme"] = "Funny";
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
function executeAndRenderMemes(mysqli_stmt $stmt): string {
|
||||
global $routerConfig;
|
||||
$stmt->execute();
|
||||
$stmt->store_result();
|
||||
$stmt->bind_result($memeID, $title, $textContent, $createdAt, $authorID, $filePath, $imageWidth, $imageHeight, $userNickname);
|
||||
|
||||
$memes_out = '';
|
||||
$meme_template = file_get_contents($routerConfig['template_dir'] . "meme.html");
|
||||
$meme_gallery_template = file_get_contents($routerConfig['template_dir'] . 'meme_gallery.html');
|
||||
|
||||
while ($stmt->fetch()) {
|
||||
$memes_out .= renderMeme($memeID, $authorID, $title, $textContent, $createdAt, $filePath, $imageWidth, $imageHeight, $userNickname, $meme_template);
|
||||
}
|
||||
|
||||
$meme_add = isLoggedIn() ? file_get_contents($routerConfig['template_dir'] . 'meme_add.html') : '';
|
||||
$meme_gallery_out = str_replace('__TEMPLATE_MEMES_HERE__', $memes_out, $meme_gallery_template);
|
||||
$meme_gallery_out = str_replace('__TEMPLATE_MEME_ADD__', $meme_add, $meme_gallery_out);
|
||||
|
||||
$stmt->close();
|
||||
return $meme_gallery_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a meme into HTML based on provided data and a template.
|
||||
*
|
||||
* @param int $id The ID of the meme.
|
||||
* @param int $authorId The author's user ID.
|
||||
* @param string $title The title of the meme.
|
||||
* @param string $textContent The text content of the meme.
|
||||
* @param string $createdAt The creation timestamp of the meme.
|
||||
* @param string $filePath The file path of the associated image.
|
||||
* @param int $imageWidth The width of the image.
|
||||
* @param int $imageHeight The height of the image.
|
||||
* @param string $userNickname The nickname of the meme's author.
|
||||
* @param string $meme_template The HTML template for a meme. (used to not read the template over and over when rendering more memes)
|
||||
* @return string Returns the rendered HTML of the meme.
|
||||
*/
|
||||
function renderMeme(int $id, int $authorId, string $title, string $textContent, string $createdAt, string $filePath, int $imageWidth, int $imageHeight, string $userNickname, string $meme_template): string
|
||||
{
|
||||
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_TITLE__', htmlspecialchars($title), $meme_template);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_AUTHOR__', htmlspecialchars($userNickname), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_DATE__', htmlspecialchars($createdAt), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_IMAGE__', '/' . htmlspecialchars($filePath), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_IMAGE_WIDTH__', strval($imageWidth), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_IMAGE_HEIGHT__', strval($imageHeight), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_DELETE_BUTTON__', (isModerator() || $_SESSION['ID'] == $authorId) ? "<button onclick=\"deleteMeme($id);\"><i class='ri-delete-bin-line'></i></button>" : '', $meme_out);
|
||||
|
||||
$meme_votes = calculateNetVotes($id);
|
||||
$meme_net_votes = $meme_votes['NetVotes'];
|
||||
|
||||
if ($meme_votes['UserVote'] > 0) {
|
||||
$meme_upvote_active = 'fill';
|
||||
$meme_downvote_active = 'line';
|
||||
$meme_vote_counter_class = 'positive';
|
||||
$meme_upvote_button_class = ' visual_hover';
|
||||
$meme_downvote_button_class = '';
|
||||
} elseif (($meme_votes['UserVote'] < 0)) {
|
||||
$meme_upvote_active = 'line';
|
||||
$meme_downvote_active = 'fill';
|
||||
$meme_vote_counter_class = 'negative';
|
||||
$meme_upvote_button_class = '';
|
||||
$meme_downvote_button_class = ' visual_hover';
|
||||
} else {
|
||||
$meme_downvote_active = 'line';
|
||||
$meme_upvote_active = 'line';
|
||||
$meme_vote_counter_class = 'neutral';
|
||||
$meme_upvote_button_class = '';
|
||||
$meme_downvote_button_class = '';
|
||||
}
|
||||
$meme_upvote = isLoggedIn() ? "<button id='meme_votes_upvote_button_$id' class='meme_upvote$meme_upvote_button_class' onclick=\"voteMeme($id, 1);\"> <i id='meme_votes_upvote_$id' class=\"ri-arrow-up-circle-$meme_upvote_active\"></i></button>" : '';
|
||||
$meme_downvote = isLoggedIn() ? "<button id='meme_votes_downvote_button_$id' class='meme_downvote$meme_downvote_button_class' onclick=\"voteMeme($id, 0);\"> <i id='meme_votes_downvote_$id' class=\"ri-arrow-down-circle-$meme_downvote_active\"></i></button>" : '';
|
||||
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_VOTES_NUMBER__', strval($meme_net_votes), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_UPVOTE__', $meme_upvote, $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_DOWNVOTE__', $meme_downvote, $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_ID__', strval($id), $meme_out);
|
||||
$meme_out = str_replace('__TEMPLATE_MEME_VOTE_COUNTER_CLASS__', $meme_vote_counter_class, $meme_out);
|
||||
|
||||
return str_replace('__TEMPLATE_MEME_TEXT__', htmlspecialchars($textContent), $meme_out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a gallery of memes, optionally filtered by author ID, meme ID, or a keyword.
|
||||
*
|
||||
* This function retrieves memes from the database and returns an HTML string representation.
|
||||
* It supports filtering by author ID, meme ID, or a keyword that is searched in titles and text content.
|
||||
* It also supports pagination through an offset parameter.
|
||||
*
|
||||
* @param int|null $offset Pagination offset, used to calculate the starting point for records to return.
|
||||
* @param int|null $authorId Optional author ID for filtering memes by a specific author.
|
||||
* @param int|null $memeId Optional meme ID for rendering a single meme.
|
||||
* @param string|null $keyword Optional keyword for full-text search in meme titles and content.
|
||||
* @return string Returns the complete HTML content of the meme gallery, optionally filtered.
|
||||
*/
|
||||
|
||||
function getMemeGallery(?int $offset = null, ?int $authorId = null, ?int $memeId = null, ?string $keyword = null): array {
|
||||
return [
|
||||
"Status" => "Success",
|
||||
"Output" => renderMemeGallery($offset, $authorId, $memeId, $keyword)
|
||||
];
|
||||
}
|
||||
|
||||
function renderMemeGallery(?int $offset = null, ?int $authorId = null, ?int $memeId = null, ?string $keyword = null): string {
|
||||
global $mysqli, $routerConfig;
|
||||
|
||||
// Start building the SQL query
|
||||
$query = 'SELECT Memes.ID, Memes.Title, Memes.TextContent, Memes.CreatedAt, Memes.AuthorID,
|
||||
Files.Path, Files.Width, Files.Height, Users.Nickname
|
||||
FROM Memes
|
||||
INNER JOIN Users ON Memes.AuthorID = Users.ID
|
||||
INNER JOIN Files ON Memes.FileID = Files.ID';
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
$types = '';
|
||||
|
||||
// Add conditions based on provided parameters
|
||||
if ($authorId !== null) {
|
||||
$conditions[] = 'Memes.AuthorID = ?';
|
||||
$params[] = $authorId;
|
||||
$types .= 'i';
|
||||
}
|
||||
if ($memeId !== null) {
|
||||
$conditions[] = 'Memes.ID = ?';
|
||||
$params[] = $memeId;
|
||||
$types .= 'i';
|
||||
}
|
||||
if ($keyword !== null) {
|
||||
$conditions[] = '(Memes.Title LIKE CONCAT("%", ?, "%") OR Memes.TextContent LIKE CONCAT("%", ?, "%"))';
|
||||
$params[] = $keyword;
|
||||
$params[] = $keyword;
|
||||
$types .= 'ss';
|
||||
}
|
||||
|
||||
// Append conditions to the query
|
||||
if (!empty($conditions)) {
|
||||
$query .= ' WHERE ' . join(' AND ', $conditions);
|
||||
}
|
||||
|
||||
if($offset == null) {
|
||||
$offset = 0;
|
||||
}
|
||||
// Add pagination and limit
|
||||
$query .= ' LIMIT ? OFFSET ?';
|
||||
$params[] = $routerConfig['meme']['per_page'];
|
||||
$params[] = $routerConfig['meme']['per_page'] * $offset;
|
||||
$types .= 'ii';
|
||||
|
||||
$stmt = $mysqli->prepare($query);
|
||||
$stmt->bind_param($types, ...$params);
|
||||
return executeAndRenderMemes($stmt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a meme from the database if the current user has the right permissions.
|
||||
*
|
||||
* @param int $memeID The ID of the meme to delete.
|
||||
* @return array Returns an associative array with the status of the operation.
|
||||
* @global mysqli $mysqli The database connection object.
|
||||
*/
|
||||
function deleteMeme(int $memeID): array
|
||||
{
|
||||
global $mysqli;
|
||||
$out = ["Status" => "Fail"];
|
||||
if (isLoggedIn()) {
|
||||
$query = !isModerator() ? 'DELETE FROM Memes WHERE ID = ? AND AuthorID = ?' : 'DELETE FROM Memes WHERE ID = ?';
|
||||
$stmtDelete = $mysqli->prepare($query);
|
||||
if (!isModerator()) {
|
||||
$stmtDelete->bind_param('ii', $memeID, $_SESSION['ID']);
|
||||
} else {
|
||||
$stmtDelete->bind_param('i', $memeID);
|
||||
}
|
||||
$stmtDelete->execute();
|
||||
if ($stmtDelete->affected_rows > 0) {
|
||||
$out['Status'] = 'Success';
|
||||
}
|
||||
$stmtDelete->close();
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records or updates a vote on a meme by the current user.
|
||||
*
|
||||
* @param int $memeID The ID of the meme to be voted on.
|
||||
* @param int $isUpvote Indicates whether the vote is an upvote (1) or downvote (0).
|
||||
* @return array Returns an associative array with the status of the vote operation.
|
||||
* @global mysqli $mysqli The database connection object.
|
||||
*/
|
||||
function voteMeme(int $memeID, int $isUpvote): array
|
||||
{
|
||||
global $mysqli;
|
||||
$out = ["Status" => "Fail"];
|
||||
if ($isUpvote != 1) {
|
||||
$isUpvote = 0;
|
||||
}
|
||||
$memeVoteConn = $mysqli->prepare('INSERT INTO MemeVotes (MemeID, UserID, isUpvote) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE isUpvote = VALUES(isUpvote)');
|
||||
$memeVoteConn->bind_param('iii', $memeID, $_SESSION['ID'], $isUpvote);
|
||||
$memeVoteConn->execute();
|
||||
if ($memeVoteConn->affected_rows > 0) {
|
||||
$out['Status'] = 'Success';
|
||||
}
|
||||
$memeVoteConn->close();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a vote previously made by the current user on a meme.
|
||||
*
|
||||
* @param int $memeID The ID of the meme whose vote is to be deleted.
|
||||
* @return array Returns an associative array with the status of the deletion.
|
||||
* @global mysqli $mysqli The database connection object.
|
||||
*/
|
||||
function deleteVoteMeme(int $memeID): array
|
||||
{
|
||||
global $mysqli;
|
||||
$out = ["Status" => "Fail"];
|
||||
$memeVoteConn = $mysqli->prepare('DELETE FROM MemeVotes WHERE MemeID = ? AND UserID = ?');
|
||||
$memeVoteConn->bind_param('ii', $memeID, $_SESSION['ID']);
|
||||
$memeVoteConn->execute();
|
||||
if ($memeVoteConn->affected_rows > 0) {
|
||||
$out['Status'] = 'Success';
|
||||
}
|
||||
$memeVoteConn->close();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the net votes for a meme and determines if the current user has voted on it.
|
||||
* The array has both the net votes and the user vote(0 when the user hasn't voted)
|
||||
*
|
||||
* @param int $memeID The ID of the meme for which votes are being calculated.
|
||||
* @return array Returns an array with net votes and the user's vote status.
|
||||
* @global mysqli $mysqli The database connection object.
|
||||
*/
|
||||
function calculateNetVotes(int $memeID): array
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
// Adjusted query to calculate net votes and get the user's vote in one go
|
||||
$query = "
|
||||
SELECT
|
||||
SUM(CASE WHEN isUpvote = 1 THEN 1 ELSE -1 END) AS NetVotes,
|
||||
(
|
||||
SELECT CASE WHEN isUpvote = 1 THEN 1 ELSE -1 END
|
||||
FROM MemeVotes
|
||||
WHERE MemeID = ? AND UserID = ?
|
||||
) AS UserVote
|
||||
FROM MemeVotes
|
||||
WHERE MemeID = ?";
|
||||
|
||||
$stmt = $mysqli->prepare($query);
|
||||
$userID = $_SESSION['ID'];
|
||||
$stmt->bind_param('iii', $memeID, $userID, $memeID);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$data = $result->fetch_assoc();
|
||||
|
||||
$netVotes = $data['NetVotes'] ?? 0; // Null coalescing operator in case no votes are found
|
||||
$userVote = $data['UserVote'] ?? 0; // Default to 0 if the user hasn't voted
|
||||
|
||||
$stmt->close();
|
||||
|
||||
return [
|
||||
"NetVotes" => $netVotes,
|
||||
"UserVote" => $userVote
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the net votes and user's vote status for a specific meme.
|
||||
* Essentially just a wrapper of getMemeVotes for an API call
|
||||
*
|
||||
* @param int $memeID The ID of the meme to fetch votes for.
|
||||
* @return array Returns an array with the net votes and the user's vote status, along with operation status.
|
||||
*/
|
||||
function getMemeVotes(int $memeID): array
|
||||
{
|
||||
$voteData = calculateNetVotes($memeID);
|
||||
return [
|
||||
"Status" => "Success",
|
||||
"NetVotes" => $voteData['NetVotes'],
|
||||
"UserVote" => $voteData['UserVote']
|
||||
];
|
||||
}
|
@ -1,53 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Includes a PHP file that returns metadata associated with a dynamic page.
|
||||
* It simply scopes an external file into a function to prevent variable conflicts.
|
||||
*
|
||||
* @param string $file The file path to the PHP file that contains metadata.
|
||||
* @return array Returns an associative array of metadata from the included PHP file.
|
||||
*/
|
||||
function getDynamicMetadata(string $file): array{
|
||||
return include($file);
|
||||
}
|
||||
/**
|
||||
* Extracts and validates the minimal permission level required to access certain content,
|
||||
* defaulting to system configuration if not properly set or in case of an error.
|
||||
*
|
||||
* @param array $metadata Metadata array that should include a 'parameters' key with 'minimal_permission_level'.
|
||||
* @return int Returns the minimal permission level required to access a page.
|
||||
*@global array $routerConfig Global router configuration settings.
|
||||
*/
|
||||
function getDynamicPermission(array $metadata): int {
|
||||
function getDynamicPermission($file): int {
|
||||
global $routerConfig;
|
||||
$params = $metadata["parameters"];
|
||||
try {
|
||||
$permission_level = $params["minimal_permission_level"];
|
||||
$page_tmp = include($file);
|
||||
$permission_level = $page_tmp["parameters"]["minimal_permission_level"];
|
||||
|
||||
if (!is_numeric($permission_level) || $permission_level <= 0) {
|
||||
$permission_level = $routerConfig["page"]["default_permissions"];
|
||||
$permission_level = $routerConfig["default_page_permission_level"];
|
||||
}
|
||||
}
|
||||
catch (Exception){
|
||||
$permission_level = $routerConfig["page"]["default_permissions"];
|
||||
$permission_level = $routerConfig["default_page_permission_level"];
|
||||
} finally {
|
||||
return $permission_level;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generates HTML navigation links for all sites and pages configured in the router,
|
||||
* adjusting active states based on current request and filtering links by user permissions.
|
||||
*
|
||||
* @global array $routerConfig Global configuration that includes directory paths and default settings.
|
||||
* @global array $routerRequest Current request details including site and page name.
|
||||
* @return string Returns the HTML string of the navigation menu.
|
||||
*/
|
||||
|
||||
function generateNavigation(): string
|
||||
{
|
||||
global $routerConfig;
|
||||
global $routerRequest;
|
||||
|
||||
$nav = file_get_contents($routerConfig["template_dir"] . "nav.html");
|
||||
$site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..'));
|
||||
|
||||
$nav_out = "";
|
||||
@ -57,11 +31,16 @@ function generateNavigation(): string
|
||||
|
||||
$site_name = str_replace("_", " ", $site_dir);
|
||||
|
||||
$site_subdomain = $site_dir . ".";
|
||||
if ($site_name == $routerConfig["default_site"]) {
|
||||
$site_subdomain = "";
|
||||
}
|
||||
|
||||
$site_name = ucfirst($site_name);
|
||||
|
||||
$site_location = "/" . $site_dir . "/" . $routerConfig["default_page"];
|
||||
$site_location = $routerConfig["protocol"] . $site_subdomain . $routerRequest["domain"] . "." . $routerRequest["tld"] . "/" . $routerConfig["default_page"];
|
||||
|
||||
if ($routerRequest["site_name"] == $site_dir) {
|
||||
if ($routerRequest["subdomain"] == $site_dir) {
|
||||
//this is the current page
|
||||
$site_class = "class=\"navsite_link active\"";
|
||||
}
|
||||
@ -69,22 +48,21 @@ function generateNavigation(): string
|
||||
$site_class = "class=\"navsite_link\"";
|
||||
}
|
||||
|
||||
$navigation_pages = "";
|
||||
$navpages = "";
|
||||
|
||||
foreach ($pages_dir as $page_file) {
|
||||
$page_file_tmp = explode(".", $page_file);
|
||||
$page_basename = $page_file_tmp[0];
|
||||
$page_class = "class=\"navpage_link\"";
|
||||
if ($routerRequest["site_name"] == $site_dir && $routerRequest["page_name"] == $page_basename) {
|
||||
if ($routerRequest["subdomain"] == $site_dir && $routerRequest["page_name"] == $page_basename) {
|
||||
$page_class = "class=\"navpage_link active\"";
|
||||
}
|
||||
|
||||
$page_location = "/" . $site_dir . "/" . $page_basename;
|
||||
$page_location = $routerConfig["protocol"] . $site_subdomain . $routerRequest["domain"] . "." . $routerRequest["tld"] . "/" . $page_basename;
|
||||
|
||||
$page_name = str_replace("_", " ", $page_basename);
|
||||
$page_name = explode(".", $page_name)[0];
|
||||
$page_name = ucfirst($page_name);
|
||||
|
||||
$page_file_path = $routerConfig["page_dir"] . $site_dir . "/" . $page_file ;
|
||||
if($page_file_tmp[1] == "html"){
|
||||
$page_tmp = file_get_contents($page_file_path);
|
||||
@ -94,48 +72,26 @@ function generateNavigation(): string
|
||||
$page_required_permission = intval($pageMetadata["parameters"]["minimal_permission_level"]);
|
||||
}
|
||||
else{
|
||||
$page_required_permission = $routerConfig["page"]["default_permissions"];
|
||||
}
|
||||
if(!empty($pageMetadata["parameters"]["page_title"])){
|
||||
$page_name = $pageMetadata["parameters"]["page_title"];
|
||||
$page_required_permission = $routerConfig["default_page_permission_level"];
|
||||
}
|
||||
}
|
||||
elseif($page_file_tmp[1] == "php"){
|
||||
$pageMetadata = getDynamicMetadata($page_file_path);
|
||||
$page_required_permission = getDynamicPermission($pageMetadata);
|
||||
|
||||
if(!empty($pageMetadata["parameters"]["page_title"])){
|
||||
$page_name = $pageMetadata["parameters"]["page_title"];
|
||||
}
|
||||
$page_required_permission = getDynamicPermission($page_file_path);
|
||||
}
|
||||
else{
|
||||
$page_required_permission = $routerConfig["page"]["default_permissions"];
|
||||
$page_required_permission = $routerConfig["default_page_permission_level"];
|
||||
}
|
||||
|
||||
|
||||
if($page_required_permission <= $_SESSION["privilege_level"]) {
|
||||
$navpage_attributes = "data-site='$site_dir' data-page='$page_basename'";
|
||||
$navigation_pages .= "<li class='navpage_item' $navpage_attributes ><a $navpage_attributes href='$page_location' $page_class>$page_name</a></li>";
|
||||
$navpages .= "<li class='navpage_item' data-page='$page_basename'><a href='$page_location' $page_class>$page_name</a></li>";
|
||||
}
|
||||
}
|
||||
if(!empty($navigation_pages)){
|
||||
$default_page = $routerConfig["default_page"];
|
||||
$navsite_attributes = "data-page='$default_page' data-site='$site_dir'";
|
||||
$nav_out .= "<li class='navsite_item' ><a $navsite_attributes href='$site_location' $site_class>$site_name</a><ul class='navpage_list'>$navigation_pages</ul></li>";
|
||||
if(!empty($navpages)){
|
||||
$nav_out .= "<li class='navsite_item' data-site='$site_dir'><a href='$site_location' $site_class>$site_name</a><ul class='navpage_list'>$navpages</ul></li>";
|
||||
}
|
||||
}
|
||||
|
||||
return str_replace("__NAV_PAGES__", $nav_out, $nav);
|
||||
return $nav_out;
|
||||
}
|
||||
/**
|
||||
* Provides a simple API endpoint-like response for fetching generated navigation HTML.
|
||||
* Wraps generateNavigation for an API.
|
||||
*
|
||||
* @return array Returns an associative array with the navigation HTML and a status indicating success.
|
||||
*/
|
||||
function getNavigationEndpoint() :array{
|
||||
return [
|
||||
"Status" => "Success",
|
||||
"Navigation" => generateNavigation(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,130 +0,0 @@
|
||||
<?php
|
||||
require_once "lib/account.php";
|
||||
/**
|
||||
* Retrieves news articles based on the current user's privilege level.
|
||||
* The function queries the NewsArticles and Users tables to fetch articles
|
||||
* that the user has the privilege to view. Articles are joined with user
|
||||
* information to include the author's nickname.
|
||||
*
|
||||
* @global mysqli $mysqli The mysqli database connection object.
|
||||
* @return array Returns an associative array with a status key indicating the success or failure,
|
||||
* and an 'Articles' key containing an array of articles if successful.
|
||||
*/
|
||||
function getNewsArticles() :array
|
||||
{
|
||||
global $mysqli;
|
||||
|
||||
$output = ["Status" => "Fail"]; // Default Status is "Fail"
|
||||
|
||||
$articles = [];
|
||||
$stmt = $mysqli->prepare("SELECT NewsArticles.ID, NewsArticles.WrittenAt, NewsArticles.WrittenBy, NewsArticles.Title, NewsArticles.Body, NewsArticles.FileList, Users.Nickname FROM NewsArticles INNER JOIN Users ON NewsArticles.WrittenBy = Users.ID WHERE NewsArticles.PrivilegeLevel <= ?;");
|
||||
$id = 0;
|
||||
$writtenAt = "";
|
||||
$writtenBy = 0;
|
||||
$title = "";
|
||||
$body = "";
|
||||
$filelist = 0;
|
||||
$writtenByName = "";
|
||||
|
||||
$stmt->bind_param("i", $_SESSION["privilege_level"]);
|
||||
$stmt->bind_result($id, $writtenAt, $writtenBy, $title, $body, $filelist, $writtenByName);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
while ($stmt->fetch()) {
|
||||
$articles[] = [
|
||||
'ID' => $id,
|
||||
'WrittenAt' => $writtenAt,
|
||||
'Title' => $title,
|
||||
'Body' => $body,
|
||||
'WrittenByName' =>$writtenByName
|
||||
];
|
||||
}
|
||||
|
||||
// Check if any results were fetched
|
||||
if (!empty($articles)) {
|
||||
$output["Status"] = "Success";
|
||||
$output["Articles"] = $articles;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new news article to the database if the user is logged in and has the appropriate
|
||||
* privilege level. The function sanitizes the title and body of the article to prevent XSS attacks.
|
||||
*
|
||||
* @global mysqli $mysqli The mysqli database connection object.
|
||||
* @global array $routerConfig Configuration array that includes default permission settings.
|
||||
* @param string $title The title of the news article. Default value is "Nazov".
|
||||
* @param string $body The body of the news article. Default value is "Obsah".
|
||||
* @param int $privilegeLevel The privilege level required to view the article. If set to 0, uses default from configuration.
|
||||
* @return array Returns an associative array with a status key that indicates the success or failure of the operation.
|
||||
*/
|
||||
function addNewsArticle(string $title="Nazov", string $body="Obsah", int $privilegeLevel=0) :array
|
||||
{
|
||||
global $mysqli;
|
||||
global $routerConfig;
|
||||
|
||||
|
||||
if ($privilegeLevel == 0){
|
||||
$privilegeLevel = $routerConfig['newsarticle']['default_permissions'];
|
||||
}
|
||||
|
||||
$output = ["Status" => "Fail"]; // Default Status is "Fail"
|
||||
if (isLoggedIn() && $privilegeLevel <= $_SESSION["privilege_level"]) {
|
||||
$query = $mysqli->prepare("INSERT INTO NewsArticles (WrittenBy, Title, Body, FileList, PrivilegeLevel) VALUES (?, ?, ?, 0, ?);");
|
||||
$minpriv = intval($privilegeLevel);
|
||||
$query->bind_param("issi", $_SESSION["ID"], htmlspecialchars($title), htmlspecialchars($body), $minpriv);
|
||||
$query->execute();
|
||||
if ($query->affected_rows > 0) {
|
||||
$output["Status"] = "Success";
|
||||
}
|
||||
$query->close();
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a comment to a news article.
|
||||
*
|
||||
* @param int $userId User who is commenting.
|
||||
* @param int $newsArticleId ID of the news article.
|
||||
* @param string $commentText The content of the comment.
|
||||
* @param int|null $parentId ID of the parent comment if it's a reply.
|
||||
* @return array Status array indicating success or failure.
|
||||
* @global mysqli $mysqli The mysqli database connection object.
|
||||
*/
|
||||
function addNewsComment(int $userId, int $newsArticleId, string $commentText, ?int $parentId = null): array {
|
||||
global $mysqli;
|
||||
|
||||
$output = ["Status" => "Fail"]; // Default Status is "Fail"
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
$output['Error'] = "User must be logged in.";
|
||||
return $output;
|
||||
}
|
||||
|
||||
// Prepare the SQL statement to prevent SQL injection
|
||||
$stmt = $mysqli->prepare("INSERT INTO NewsComments (ParentID, UserID, NewsArticleID, CommentText) VALUES (?, ?, ?, ?);");
|
||||
|
||||
// Bind parameters. 'i' denotes an integer and 's' denotes a string.
|
||||
$stmt->bind_param("iiis", $parentId, $userId, $newsArticleId, $commentText);
|
||||
|
||||
// Execute the query
|
||||
if ($stmt->execute()) {
|
||||
// Check if any rows were affected
|
||||
if ($stmt->affected_rows > 0) {
|
||||
$output["Status"] = "Success";
|
||||
} else {
|
||||
$output["Error"] = "No rows affected.";
|
||||
}
|
||||
} else {
|
||||
$output["Error"] = $stmt->error;
|
||||
}
|
||||
|
||||
// Close statement
|
||||
$stmt->close();
|
||||
|
||||
return $output;
|
||||
}
|
194
lib/page.php
194
lib/page.php
@ -1,37 +1,11 @@
|
||||
<?php
|
||||
require_once "lib/dynamic_style.php";
|
||||
require_once "lib/script_data.php";
|
||||
/**
|
||||
* Loads and returns the result of a PHP file.
|
||||
* This function is typically used to process dynamic content of a page.
|
||||
* It simply scopes an external file into a function to prevent variable conflicts.
|
||||
*
|
||||
* @param string $page_file The file path to the dynamic page.
|
||||
* @return array Returns the array of data generated by including the PHP file.
|
||||
*/
|
||||
function renderDynamicPage(string $page_file): array
|
||||
require_once "lib/inliner.php";
|
||||
function renderDynamicPage($page_file): array
|
||||
{
|
||||
return require $page_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all HTML comments from the provided content string.
|
||||
*
|
||||
* @param string $content The HTML content from which to remove comments.
|
||||
* @return string The content without any HTML comments.
|
||||
*/
|
||||
function removeHtmlComments(string $content = '') :string {
|
||||
return preg_replace('/<!--(.|\s)*?-->/', '', $content);
|
||||
}
|
||||
/**
|
||||
* Parses custom `<page>` tags from the given input string and extracts parameters.
|
||||
* Returns the input string with `<page>` tags removed and a list of parameters.
|
||||
*
|
||||
* @param string $input The input HTML or text containing `<page>` tags.
|
||||
* @return array Returns an associative array with 'parameters' (parsed from the tag)
|
||||
* and 'output' (the modified input string with `<page>` tags removed).
|
||||
*/
|
||||
function parsePageTag(string $input): array
|
||||
function parsePageTag($input): array
|
||||
{
|
||||
// Define the pattern for the tag
|
||||
$pattern = '/<page\s+([^>]+)><\/page>/i';
|
||||
@ -55,33 +29,21 @@ function parsePageTag(string $input): array
|
||||
// If no match is found, return the original input
|
||||
return ['parameters' => [], 'output' => $input];
|
||||
}
|
||||
/**
|
||||
* Renders a page based on specified page and site names, handling dynamic and static content,
|
||||
* permissions, and error pages.
|
||||
*
|
||||
* @param string|null $page_name The name of the page to render. If null, uses default from request.
|
||||
* @param string|null $site_name The name of the site to render. If null, uses default from request.
|
||||
* @return array Returns an associative array containing the rendered page content, page name, site name, and page title.
|
||||
*/
|
||||
function renderPage(string $page_name = null, string $site_name = null): array
|
||||
|
||||
function getPage($page_name = null): array|false|string
|
||||
{
|
||||
global $routerConfig;
|
||||
global $routerRequest;
|
||||
|
||||
if(!$site_name) {
|
||||
$site_name = $routerRequest["site_name"];
|
||||
}
|
||||
|
||||
$site_title = str_replace("_", " ", $site_name);
|
||||
|
||||
$site_title = ucfirst($site_title);
|
||||
|
||||
if(!$page_name){
|
||||
$page_name = $routerRequest["page_name"];
|
||||
}
|
||||
|
||||
$dynamic_page_file = $routerConfig["page_dir"] . $site_name . "/" . $page_name . ".php";
|
||||
$page_file = $routerConfig["page_dir"] . $site_name . "/" . $page_name . ".html";
|
||||
$dynamic_page_file = $routerConfig["page_dir"] . $routerRequest["subdomain"] . "/" . $page_name . ".php";
|
||||
$page_file = $routerConfig["page_dir"] . $routerRequest["subdomain"] . "/" . $page_name . ".html";
|
||||
|
||||
$skeleton = file_get_contents($routerConfig["template_dir"] . "skeleton.html");
|
||||
$nav = file_get_contents($routerConfig["template_dir"] . "nav.html");
|
||||
|
||||
if (file_exists($dynamic_page_file)){
|
||||
$pageMetadata = renderDynamicPage($dynamic_page_file);
|
||||
@ -105,7 +67,7 @@ function renderPage(string $page_name = null, string $site_name = null): array
|
||||
$page_required_permission = intval($pageMetadata["parameters"]["minimal_permission_level"]);
|
||||
}
|
||||
else{
|
||||
$page_required_permission = $routerConfig["page"]["default_permissions"];
|
||||
$page_required_permission = $routerConfig["default_page_permission_level"];
|
||||
}
|
||||
|
||||
if(!empty($pageMetadata["parameters"]["secret"])){
|
||||
@ -117,11 +79,11 @@ function renderPage(string $page_name = null, string $site_name = null): array
|
||||
$is_secret_page = 0;
|
||||
}
|
||||
else{
|
||||
$is_secret_page = $routerConfig["page"]["default_secret"];
|
||||
$is_secret_page = $routerConfig["default_page_secret"];
|
||||
}
|
||||
}
|
||||
else{
|
||||
$is_secret_page = $routerConfig["page"]["default_secret"];
|
||||
$is_secret_page = $routerConfig["default_page_secret"];
|
||||
}
|
||||
|
||||
|
||||
@ -140,7 +102,6 @@ function renderPage(string $page_name = null, string $site_name = null): array
|
||||
}
|
||||
}
|
||||
|
||||
$page = str_replace("__DEFAULT_LINK__", "/" . $routerConfig["default_site"] . "/" . $routerConfig["default_page"], $page);
|
||||
if(!is_string($page)){
|
||||
$page = "";
|
||||
}
|
||||
@ -152,129 +113,22 @@ function renderPage(string $page_name = null, string $site_name = null): array
|
||||
$page_title = $page_name;
|
||||
}
|
||||
|
||||
$page_title = $routerConfig['site_prefix'] . " " . $site_title . " " . $page_title;
|
||||
|
||||
$page = str_replace("__TEMPLATE_PAGE_TITLE__", $page_title, $page);
|
||||
|
||||
$page = removeHtmlComments($page);
|
||||
|
||||
return [
|
||||
"PageContent" => $page,
|
||||
"PageName" => $page_name,
|
||||
"SiteName" => $site_name,
|
||||
"PageTitle" => $page_title,
|
||||
];
|
||||
|
||||
}
|
||||
/**
|
||||
* Compiles a complete web page by injecting dynamic elements into a template skeleton,
|
||||
* including headers, footers, and SEO tags.
|
||||
* It is used when not going to a page by AJAX to initialize everything.
|
||||
*
|
||||
* @param string|null $site_name_in The site name to be used; defaults from global configuration if null.
|
||||
* @param string|null $page_name_in The page name to be used; defaults from global configuration if null.
|
||||
* @return string The complete HTML content of the web page ready for display.
|
||||
*/
|
||||
function getPage(string $site_name_in = null, string $page_name_in = null): string
|
||||
{
|
||||
$page_tmp = renderPage($page_name_in, $site_name_in);
|
||||
|
||||
$page = $page_tmp["PageContent"];
|
||||
$page_name = $page_tmp["PageName"];
|
||||
$site_name = $page_tmp["SiteName"];
|
||||
global $routerConfig;
|
||||
|
||||
$skeleton = file_get_contents($routerConfig["template_dir"] . "skeleton.html");
|
||||
|
||||
$footer = file_get_contents($routerConfig["template_dir"] . "footer.html");
|
||||
|
||||
$page_title = $page_tmp["PageTitle"];
|
||||
$dynamic_style = doDynamicStyling();
|
||||
$dynamic_script_data = [
|
||||
"currentPage" => $page_name,
|
||||
"currentSite" => $site_name,
|
||||
"currentTitle" => $page_title,
|
||||
"defaultPage" => $routerConfig["default_page"],
|
||||
"defaultSite" => $routerConfig["default_site"],
|
||||
"UserInfo_Privileges" => $_SESSION["privilege_level"],
|
||||
];
|
||||
if(isLoggedIn()){
|
||||
$dynamic_script_data += [
|
||||
"UserInfo_FirstName" => $_SESSION["first_name"],
|
||||
"UserInfo_LastName" => $_SESSION["last_name"],
|
||||
"UserInfo_Nickname" => $_SESSION["nickname"],
|
||||
"UserInfo_Email" => $_SESSION["email"],
|
||||
"UserInfo_MinecraftNick" => $_SESSION["minecraft_nickname"],
|
||||
];
|
||||
$dynamic_style = "<style>";
|
||||
if(isLoggedIn() && !empty($_SESSION["favorite_color"]) && is_int($_SESSION["favorite_color"]) && $_SESSION["favorite_color"] <= 4294967295){
|
||||
$color = dechex($_SESSION["favorite_color"]);
|
||||
$dynamic_style .= "--root{ --favorite-color: #$color;";
|
||||
}
|
||||
$dynamic_script = generateScriptData($dynamic_script_data);
|
||||
$dynamic_style .= "</style>";
|
||||
|
||||
$navigation = generateNavigation();
|
||||
$navpages = generateNavigation();
|
||||
|
||||
$seo = array();
|
||||
|
||||
|
||||
if(!empty($pageMetadata["parameters"]["author"])){
|
||||
$seo["author"] = htmlspecialchars($pageMetadata["parameters"]["author"]);
|
||||
}
|
||||
else{
|
||||
$seo["author"] = $routerConfig["seo"]["author"];
|
||||
}
|
||||
|
||||
if(!empty($pageMetadata["parameters"]["description"])){
|
||||
$seo["description"] = htmlspecialchars($pageMetadata["parameters"]["description"]);
|
||||
}
|
||||
else{
|
||||
$seo["description"] = $routerConfig["seo"]["description"];
|
||||
}
|
||||
|
||||
if(!empty($pageMetadata["parameters"]["keywords"])){
|
||||
$seo["keywords"] = $routerConfig["seo"]["keywords"] . ", " . htmlspecialchars($pageMetadata["parameters"]["keywords"]);
|
||||
}
|
||||
else{
|
||||
$seo["keywords"] = $routerConfig["seo"]["keywords"];
|
||||
}
|
||||
|
||||
$seo["generator"] = $routerConfig["seo"]["generator"];
|
||||
|
||||
$seo["robots"] = $routerConfig["seo"]["robots"];
|
||||
|
||||
$seo_stuff = "";
|
||||
|
||||
foreach ($seo as $key => $value){
|
||||
$seo_stuff .= "<meta name='$key' content='$value'>\n";
|
||||
}
|
||||
$nav = str_replace("__NAV_PAGES__", $navpages, $nav);
|
||||
|
||||
$out = $skeleton;
|
||||
$out = str_replace("__TEMPLATE__NAV__", $navigation, $out);
|
||||
$out = str_replace("__TEMPLATE__NAV__", $nav, $out);
|
||||
$out = str_replace("__TEMPLATE__PAGE__", $page, $out);
|
||||
$out = str_replace("__TEMPLATE__FOOTER__", $footer, $out);
|
||||
$out = str_replace("__TEMPLATE__DYNAMIC__SCRIPT__", $dynamic_script, $out);
|
||||
$out = str_replace("__TEMPLATE__DYNAMIC__STYLE__", $dynamic_style, $out);
|
||||
$out = str_replace("__TEMPLATE_SEO_STUFF__", $seo_stuff, $out);
|
||||
if($routerConfig["inlining"]) {
|
||||
require_once "lib/inliner.php";
|
||||
$out = inlineLocalStylesFromHref($out);
|
||||
$out = inlineScriptFromSrc($out);
|
||||
}
|
||||
$out = str_replace("__TEMPLATE__DYNASTYLE__", $dynamic_style, $out);
|
||||
$out = inlineLocalStylesFromHref($out);
|
||||
$out = inlineScriptFromSrc($out);
|
||||
return str_replace("__TEMPLATE_PAGE_TITLE__", $page_title, $out);
|
||||
}
|
||||
/**
|
||||
* Provides an API interface to get page details including content and meta-information for routing purposes.
|
||||
* This is what enables the page to never refresh.
|
||||
*
|
||||
* @param string $page_name The name of the page.
|
||||
* @param string $site_name The name of the site.
|
||||
* @return array Returns an array with status, page content, location URL, and title for the requested page.
|
||||
*/
|
||||
function getPageEndpoint(string $page_name, string $site_name) :array
|
||||
{
|
||||
$page_location = "/" . $site_name . "/" . $page_name;
|
||||
$page_tmp = renderPage($page_name, $site_name);
|
||||
return [
|
||||
"Status" => "Success",
|
||||
"Page" => $page_tmp["PageContent"],
|
||||
"PageLocation" => $page_location,
|
||||
"PageTitle" => $page_tmp["PageTitle"],
|
||||
];
|
||||
}
|
@ -1,46 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Initializes the routing system for a web application. This function processes the incoming
|
||||
* URL and determines the site name and page name based on the configuration and the URL structure.
|
||||
* It handles default configurations and supports different types of requests like API or page requests.
|
||||
*
|
||||
* @global array $routerConfig The global configuration array that includes default site and page settings.
|
||||
* @return array Returns an associative array containing the routing information, including the site name,
|
||||
* page name, request type, and parsed request address from the HTTP host.
|
||||
*/
|
||||
function initRouter(): array
|
||||
|
||||
function initRouter(): bool
|
||||
{
|
||||
global $routerRequest;
|
||||
global $routerConfig;
|
||||
$routerRequest = array();
|
||||
|
||||
$routerRequest["requestAddress"] = array_slice(explode('.', $_SERVER['HTTP_HOST']), -3, 3); //get the last 3 elements
|
||||
|
||||
$request_uri = explode("/", $_SERVER["QUERY_STRING"]);
|
||||
$needsRedirect = false;
|
||||
|
||||
$request_uri = array_slice($request_uri, -3, 3);
|
||||
|
||||
|
||||
$routerRequest["site_name"] = $routerConfig["default_site"];
|
||||
$routerRequest["page_name"] = $routerConfig["default_page"];
|
||||
|
||||
if(count($request_uri) > 2){
|
||||
$routerRequest["page_name"] = basename($request_uri[2]);
|
||||
}
|
||||
if(count($request_uri) > 1){
|
||||
$routerRequest["site_name"] = basename($request_uri[1]);
|
||||
if(count($routerRequest["requestAddress"]) < 3){
|
||||
// Root domain accessed directly
|
||||
$routerRequest["subdomain"] = $routerConfig["default_site"];
|
||||
$routerRequest["domain"] = basename($routerRequest["requestAddress"][0]);
|
||||
$routerRequest["tld"] = basename($routerRequest["requestAddress"][1]);
|
||||
$routerRequest["isToApex"] = true;
|
||||
|
||||
}
|
||||
else {
|
||||
$routerRequest["subdomain"] = basename($routerRequest["requestAddress"][0]);
|
||||
$routerRequest["domain"] = basename($routerRequest["requestAddress"][1]);
|
||||
$routerRequest["tld"] = basename($routerRequest["requestAddress"][2]);
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST"){
|
||||
$routerRequest["type"] = "api";
|
||||
if($routerRequest["subdomain"] == $routerConfig["default_site"]){
|
||||
$routerRequest["subdomain"] = "";
|
||||
$needsRedirect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($routerRequest["type"])) {
|
||||
$routerRequest["type"] = "page";
|
||||
$routerRequest["page_name"] = basename($_SERVER["QUERY_STRING"]);
|
||||
|
||||
if (empty($routerRequest["page_name"])) {
|
||||
// Page name is empty
|
||||
$needsRedirect = true;
|
||||
$routerRequest["page_name"] = $routerConfig["default_page"];
|
||||
}
|
||||
|
||||
return $routerRequest;
|
||||
|
||||
if ($needsRedirect) {
|
||||
if(!empty($routerRequest["subdomain"])){
|
||||
$sub_domain = $routerRequest["subdomain"] . ".";
|
||||
}
|
||||
else{
|
||||
$sub_domain = "";
|
||||
}
|
||||
$redirectAddress = $routerConfig["protocol"] .
|
||||
$sub_domain .
|
||||
$routerRequest["domain"] . "." .
|
||||
$routerRequest["tld"] . "/" .
|
||||
$routerRequest["page_name"];
|
||||
// Redirect with default page name
|
||||
header("Location: $redirectAddress");
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
if($_SERVER["REQUEST_METHOD"] == "POST"){
|
||||
$routerRequest["type"] = "api";
|
||||
}
|
||||
if(empty($routerRequest["type"])){
|
||||
$routerRequest["type"] = "page";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Generates a JavaScript script tag containing commands to store PHP array key-value pairs in local storage.
|
||||
* This function is designed to translate PHP associative array data into JavaScript local storage items.
|
||||
* It ensures that the array is associative and single-level before proceeding with the JavaScript code generation.
|
||||
* This is used when dumping session data into local storage for use by the client script.
|
||||
*
|
||||
* @param array $phpArray The associative array whose data will be converted into JavaScript local storage setItem calls.
|
||||
* @return string Returns a script tag with JavaScript code. If the input is not a valid single-level associative array,
|
||||
* it returns a script with an error logged to the console.
|
||||
*/
|
||||
function generateScriptData(array $phpArray):string {
|
||||
// Check if the array is associative and single-level
|
||||
if (is_array($phpArray) && count($phpArray) > 0 && count(array_filter(array_keys($phpArray), 'is_string')) === count($phpArray)) {
|
||||
// Generate JavaScript code to save each array element to local storage
|
||||
$out = "<script>";
|
||||
foreach ($phpArray as $key => $value) {
|
||||
$escapedKey = addslashes(strval($key)); // Escape special characters in the key
|
||||
$escapedValue = addslashes(strval($value)); // Escape special characters in the value
|
||||
|
||||
$out .= "localStorage.setItem('$escapedKey', '$escapedValue');";
|
||||
}
|
||||
$out.= "</script>";
|
||||
} else {
|
||||
$out = "<script>console.error('Invalid PHP array. Must be single-level and associative.');</script>";
|
||||
}
|
||||
return $out;
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
require_once "lib/account.php";
|
||||
/**
|
||||
* Generates an XML sitemap as a string for a website, considering only pages that the current session
|
||||
* has sufficient privileges to access. It scans directories specified in the router configuration
|
||||
* for .html and .php files, and constructs a sitemap entry for each accessible page based on their
|
||||
* required permission levels. This function returns the sitemap as a string and
|
||||
* sets the appropriate header for XML content.
|
||||
*
|
||||
* @global array $routerConfig The global configuration array containing directory paths and default settings.
|
||||
* @return string The XML sitemap content, properly formatted in accordance with the sitemap protocol.
|
||||
*/
|
||||
function generateSitemap(): string{
|
||||
global $routerConfig;
|
||||
|
||||
$site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..'));
|
||||
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
$subdomain = ""; // You may need to modify this based on your subdomain logic
|
||||
|
||||
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
|
||||
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . PHP_EOL;
|
||||
|
||||
foreach ($site_dirs as $site_dir) {
|
||||
$pages_dir = array_diff(scandir($routerConfig["page_dir"] . $site_dir), array('.', '..'));
|
||||
|
||||
foreach ($pages_dir as $page_file) {
|
||||
$page_file_tmp = explode(".", $page_file);
|
||||
$page_basename = $page_file_tmp[0];
|
||||
$page_file_path = $routerConfig["page_dir"] . $site_dir . "/" . $page_file;
|
||||
|
||||
$page_location = $protocol . "://" . $subdomain . $domain . "/" . $site_dir . "/" . $page_basename;
|
||||
|
||||
if ($page_file_tmp[1] == "html") {
|
||||
$page_tmp = file_get_contents($page_file_path);
|
||||
|
||||
$pageMetadata = parsePageTag($page_tmp);
|
||||
|
||||
if (!empty($pageMetadata["parameters"]["minimal_permission_level"])) {
|
||||
$page_required_permission = intval($pageMetadata["parameters"]["minimal_permission_level"]);
|
||||
} else {
|
||||
$page_required_permission = $routerConfig["page"]["default_permissions"];
|
||||
}
|
||||
} elseif ($page_file_tmp[1] == "php") {
|
||||
$pageMetadata = getDynamicMetadata($page_file_path);
|
||||
$page_required_permission = getDynamicPermission($pageMetadata);
|
||||
} else {
|
||||
$page_required_permission = $routerConfig["page"]["default_permissions"];
|
||||
}
|
||||
|
||||
// Check if the user is authorized to access the page
|
||||
if ($page_required_permission <= $_SESSION["privilege_level"]) {
|
||||
$sitemap .= '<url>' . PHP_EOL;
|
||||
$sitemap .= '<loc>' . htmlspecialchars($page_location) . '</loc>' . PHP_EOL;
|
||||
// You can add other optional tags like lastmod, changefreq, priority here if needed
|
||||
$sitemap .= '</url>' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sitemap .= '</urlset>' . PHP_EOL;
|
||||
|
||||
header('Content-type: application/xml');
|
||||
return $sitemap;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/account.php";
|
||||
function submitSurvey(int $satisfaction, int $functionality, int $content, string $comment): array
|
||||
{
|
||||
global $mysqli;
|
||||
$output = ["Status" => "Fail", "Opinion" => "Ignored unsuccessfully!"];
|
||||
if (isLoggedIn()
|
||||
&& $satisfaction >= 1 && $satisfaction <= 5
|
||||
&& $functionality >= 1 && $functionality <= 5
|
||||
&& $content >= 1 && $content <= 5
|
||||
&& !empty($comment)) {
|
||||
$stmtMemeAdd = $mysqli->prepare('INSERT INTO Survey (AuthorID, Satisfaction, Functionality, Content, Comment) VALUES (?, ?, ?, ?, ?)');
|
||||
$stmtMemeAdd->bind_param('iiiis', $_SESSION['ID'], $satisfaction, $functionality, $content, htmlspecialchars($comment));
|
||||
if ($stmtMemeAdd->execute() && $stmtMemeAdd->affected_rows > 0) {
|
||||
$output["Status"] = "Success";
|
||||
$output["Opinion"] = "Ignored successfully!";
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
350
lib/upload.php
350
lib/upload.php
@ -1,350 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Sanitizes user input to be used as a part of a file path to prevent security vulnerabilities such as directory traversal.
|
||||
*
|
||||
* @param string $userInput The input string to be sanitized.
|
||||
* @return string Returns a safe string where only alphanumeric characters, underscores, and hyphens are retained.
|
||||
* Special characters and potential path traversal payloads are removed or replaced.
|
||||
*/
|
||||
function makePathSafe(string $userInput): string
|
||||
{
|
||||
// Keep only alphanumeric characters, underscores, and hyphens
|
||||
$safeString = preg_replace('/[^\w\-]/', '', $userInput);
|
||||
|
||||
// Ensure no path traversal
|
||||
$safeString = str_replace('..', '_', $safeString);
|
||||
|
||||
// Trim leading/trailing underscores
|
||||
$safeString = trim($safeString, '_');
|
||||
|
||||
// Replace directory separator characters with underscores
|
||||
$safeString = str_replace(['/', '\\'], '_', $safeString);
|
||||
|
||||
// Limit length for safety
|
||||
return substr($safeString, 0, 255);
|
||||
}
|
||||
/**
|
||||
* Automatically rotates an image based on its EXIF data to adjust its orientation.
|
||||
*
|
||||
* @param Imagick $imagick An Imagick object representing the image to be rotated.
|
||||
* @return void
|
||||
*/
|
||||
function autoRotateImage(Imagick $imagick): void {
|
||||
// Get the current orientation of the image
|
||||
try {
|
||||
$orientation = $imagick->getImageOrientation();
|
||||
switch ($orientation) {
|
||||
case Imagick::ORIENTATION_BOTTOMRIGHT: // upside down
|
||||
$imagick->rotateimage("#000", 180); // rotate 180 degrees
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_RIGHTTOP: // 90 degrees CW
|
||||
$imagick->rotateimage("#000", 90); // rotate 90 degrees CW
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_LEFTBOTTOM: // 90 degrees CCW
|
||||
$imagick->rotateimage("#000", -90); // rotate 90 degrees CCW
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset orientation to normal after the correction
|
||||
$imagick->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
|
||||
} catch (ImagickException) {
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Processes the global $_FILES array to normalize the structure and filter out any files with errors.
|
||||
*
|
||||
* @return array Returns an array of files that are ready for further processing, structured uniformly.
|
||||
*/
|
||||
function getIncomingFiles(): array
|
||||
{
|
||||
$files = $_FILES;
|
||||
$files2 = [];
|
||||
foreach ($files as $infoArr) {
|
||||
$filesByInput = [];
|
||||
foreach ($infoArr as $key => $valueArr) {
|
||||
if (is_array($valueArr)) { // file input "multiple"
|
||||
foreach ($valueArr as $i => $value) {
|
||||
$filesByInput[$i][$key] = $value;
|
||||
}
|
||||
} else { // -> string, normal file input
|
||||
$filesByInput[] = $infoArr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$files2 = array_merge($files2, $filesByInput);
|
||||
}
|
||||
$files3 = [];
|
||||
foreach ($files2 as $file) { // let's filter empty & errors
|
||||
if (!$file['error']) $files3[] = $file;
|
||||
}
|
||||
return $files3;
|
||||
}
|
||||
/**
|
||||
* Saves file metadata in the database.
|
||||
* This creates the only record of the file existing.
|
||||
*
|
||||
* @param string $filePath The path where the file is stored.
|
||||
* @param string $fileType The MIME type of the file.
|
||||
* @param int $width The width of the image file.
|
||||
* @param int $height The height of the image file.
|
||||
* @return bool Returns true if the file metadata was successfully saved to the database, false otherwise.
|
||||
*/
|
||||
function saveUploadedFileInDatabase(string $filePath, string $fileType, int $width, int $height): bool
|
||||
{
|
||||
global $mysqli;
|
||||
$stmt = $mysqli->prepare("INSERT INTO Files (Path, Type, UploadedBy, UploadedAt, Width, Height) VALUES (?, ?, ?, NOW(), ?, ?)");
|
||||
$stmt->bind_param("ssiii", $filePath, $fileType, $_SESSION["ID"], $width, $height);
|
||||
$stmt->execute();
|
||||
$stat = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
return $stat;
|
||||
}
|
||||
/**
|
||||
* Handles the uploading process of an image, including its conversion to webp format, rotation based on orientation, and saving.
|
||||
*
|
||||
* @param string $inFile The temporary file path of the uploaded file.
|
||||
* @param string $outFile The target file path where the processed image should be saved.
|
||||
* @return bool Returns true if the file was successfully processed and saved, false otherwise.
|
||||
*/
|
||||
function doImageUpload(string $inFile, string $outFile): bool
|
||||
{
|
||||
// Create Imagick object
|
||||
$width = 0;
|
||||
$height = 0;
|
||||
try {
|
||||
$imagick = new Imagick($inFile);
|
||||
$imagick->setImageFormat('webp');
|
||||
autoRotateImage($imagick);
|
||||
$imagick->stripImage();
|
||||
$imagick->writeImage($outFile);
|
||||
$width = $imagick->getImageWidth();
|
||||
$height = $imagick->getImageHeight();
|
||||
$imagick->destroy();
|
||||
} catch (ImagickException) {
|
||||
}
|
||||
|
||||
// Check if the reencoding was successful, if yes, save into the database.
|
||||
if (file_exists($outFile)) {
|
||||
return saveUploadedFileInDatabase($outFile, 'image/webp', $width, $height);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieves a list of files from the database, optionally filtered to include only files uploaded by the current user.
|
||||
* Access to an unfiltered list (files of all users) is only available to moderators.
|
||||
*
|
||||
* @param bool $onlyMine Whether to retrieve only files uploaded by the logged-in user. If false, files from all users are returned if the user is a moderator.
|
||||
* @return array Returns an array containing file data along with a status message.
|
||||
*/
|
||||
|
||||
function listFiles(bool $onlyMine = true): array
|
||||
{
|
||||
$output = ["Status" => "Success", "Files" => []];
|
||||
require_once "lib/account.php";
|
||||
if (isLoggedIn()) {
|
||||
global $mysqli;
|
||||
if (!$onlyMine && !isModerator()) {
|
||||
$onlyMine = true;
|
||||
}
|
||||
$query = "SELECT Files.ID, Files.Path, Files.Type, Files.UploadedAt, Files.UploadedBy, Users.Nickname FROM Files INNER JOIN Users ON Files.UploadedBy = Users.ID ORDER BY UploadedAt DESC";
|
||||
|
||||
if ($onlyMine) {
|
||||
$query .= " WHERE UploadedBy = ?";
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare($query);
|
||||
if ($onlyMine) {
|
||||
$stmt->bind_param("i", $_SESSION["ID"]);
|
||||
}
|
||||
|
||||
$id = 0;
|
||||
$path = "";
|
||||
$type = "";
|
||||
$uploadedAt = "";
|
||||
$uploadedByID = 0;
|
||||
$uploadedBy = "";
|
||||
|
||||
$stmt->bind_result($id, $path, $type, $uploadedAt, $uploadedByID, $uploadedBy);
|
||||
|
||||
$stmt->execute();
|
||||
$files = array();
|
||||
// Fetch the results into the bound variables
|
||||
while ($stmt->fetch()) {
|
||||
$files[] = [
|
||||
'ID' => $id,
|
||||
'Path' => $path,
|
||||
'Type' => $type,
|
||||
'UploadedAt' => $uploadedAt,
|
||||
'UploadedByID' => $uploadedByID,
|
||||
'UploadedBy' => $uploadedBy,
|
||||
];
|
||||
}
|
||||
|
||||
// Check if any results were fetched
|
||||
$output["Files"] = $files;
|
||||
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
/**
|
||||
* Processes incoming files from the $_FILES global (after processed by getIncomingFiles), performs checks, and attempts to upload a file based on its type.
|
||||
* Currently only supports valid image files.
|
||||
*
|
||||
* @return array Returns an array indicating the success or failure ('Status' key) of the file processing operations.
|
||||
*/
|
||||
|
||||
function parseIncomingFiles(): array
|
||||
{
|
||||
$incomingFiles = getIncomingFiles();
|
||||
$success = true;
|
||||
foreach ($incomingFiles as $incomingFile) {
|
||||
if ($incomingFile["error"] == 0 && is_file($incomingFile["tmp_name"])) {
|
||||
$type = explode("/", $incomingFile["type"]);
|
||||
if ($type[0] == "image") {
|
||||
$imgFname = pathinfo($incomingFile["name"], PATHINFO_FILENAME);
|
||||
$uploadPath = getUploadPath("image", $imgFname);
|
||||
if (!empty($uploadPath)) {
|
||||
if (!doImageUpload($incomingFile["tmp_name"], $uploadPath)) {
|
||||
$success = false;
|
||||
}
|
||||
} else {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$output = ["Status" => "Fail"];
|
||||
if ($success) {
|
||||
$output["Status"] = "Success";
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* Generates a file path for uploading based on the type of the file, the ID of the uploader and the date and time of uploading.
|
||||
*
|
||||
* @param string $type The type of the file, typically used to categorize the file.
|
||||
* @param string $filename The base name of the file, used in generating the final path.
|
||||
* @return string Returns the full path for storing the file or an empty string if the type is not recognized.
|
||||
*/
|
||||
|
||||
function getUploadPath(string $type = "unknown", string $filename = "hehe"): string
|
||||
{
|
||||
$type = makePathSafe($type);
|
||||
$id = makePathSafe($_SESSION["ID"]);
|
||||
$date = makePathSafe(date("YmdHis"));
|
||||
$filename = makePathSafe($filename);
|
||||
$extension = match ($type) {
|
||||
'image' => 'webp',
|
||||
default => 'dummy',
|
||||
};
|
||||
if ($extension != "dummy") {
|
||||
$basepath = "uploads/$type/$id/$date";
|
||||
mkdir($basepath, 755, true);
|
||||
return $basepath . "/$filename.$extension";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if a file with a given ID exists in the database and does permission checks.
|
||||
* Access is granted to only the user's files, in order to access all files the onlyMine parameter
|
||||
* must be false and the user must be a moderator.
|
||||
*
|
||||
* @param int $fileId The ID of the file to check.
|
||||
* @param bool $onlyMine Whether to limit the search to files uploaded by the logged-in user.
|
||||
* @return bool|string Returns the path of the file if it exists and meets the criteria, false otherwise.
|
||||
*/
|
||||
|
||||
function fileExists(int $fileId, bool $onlyMine = true): bool|string
|
||||
{
|
||||
if (!$fileId) {
|
||||
return false;
|
||||
}
|
||||
global $mysqli;
|
||||
if (!$onlyMine && !isModerator()) {
|
||||
$onlyMine = true;
|
||||
}
|
||||
$query = 'SELECT ID, Path FROM Files WHERE ID = ?' . ($onlyMine ? ' AND UploadedBy = ?' : '');
|
||||
$stmtfileexists = $mysqli->prepare($query);
|
||||
if ($onlyMine) {
|
||||
$stmtfileexists->bind_param('ii', $fileId, $_SESSION['ID']);
|
||||
} else {
|
||||
$stmtfileexists->bind_param('i', $fileId);
|
||||
}
|
||||
$filePath = "";
|
||||
$id = null;
|
||||
$stmtfileexists->bind_result($id, $filePath);
|
||||
$stmtfileexists->execute();
|
||||
$stmtfileexists->fetch();
|
||||
if ($id != null) {
|
||||
return $filePath;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds a file to a specified group, if the user created the group or creates a new group if
|
||||
* a group with a specified ID does not exist yet
|
||||
*
|
||||
* @param int $groupId The ID of the group to which the file should be added.
|
||||
* @param int $fileId The ID of the file to add to the group.
|
||||
* @return array Returns an associative array with a 'Status' key indicating success or failure.
|
||||
*/
|
||||
function addToGroup(int $groupId, int $fileId): array
|
||||
{
|
||||
$output = ["Status" => "Fail"];
|
||||
if (!$groupId || !$fileId) {
|
||||
return $output;
|
||||
}
|
||||
global $mysqli;
|
||||
$stmtcheck = $mysqli->prepare('SELECT ID FROM FileGroups WHERE CreatorID != ? AND ID = ?');
|
||||
$stmtcheck->bind_param('ii', $_SESSION['ID'], $groupId);
|
||||
$stmtcheck->execute();
|
||||
if ($stmtcheck->affected_rows == 0) {
|
||||
if (fileExists($fileId, false)) {
|
||||
$stmtadd = $mysqli->prepare('INSERT INTO FileGroups (FileID, CreatorID, ID) VALUES (?, ?, ?)');
|
||||
$stmtadd->bind_param('iii', $fileId, $_SESSION['ID'], $groupId);
|
||||
$stmtadd->execute();
|
||||
if ($stmtadd->affected_rows > 0) {
|
||||
$output["Status"] = "Success";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
/**
|
||||
* Deletes a file entry from the database and the file system, a user can only delete his own files,
|
||||
* except when he is a moderator, in that case he can delete all files.
|
||||
*
|
||||
* @param int $fileID The ID of the file to be deleted.
|
||||
* @return array Returns an array with a 'Status' key indicating the success or failure of the deletion operation.
|
||||
*/
|
||||
function deleteFile(int $fileID): array
|
||||
{
|
||||
global $mysqli;
|
||||
$out = ["Status" => "Fail"];
|
||||
if (isLoggedIn()) {
|
||||
$file_location = fileExists($fileID, !isModerator());
|
||||
$query = !isModerator() ? 'DELETE FROM Files WHERE ID = ? AND UploadedBy = ?' : 'DELETE FROM Files WHERE ID = ?';
|
||||
$stmtDelete = $mysqli->prepare($query);
|
||||
if (!isModerator()) {
|
||||
$stmtDelete->bind_param('ii', $fileID, $_SESSION['ID']);
|
||||
} else {
|
||||
$stmtDelete->bind_param('i', $fileID);
|
||||
}
|
||||
$stmtDelete->execute();
|
||||
if ($file_location) {
|
||||
if (unlink($file_location) && $stmtDelete->affected_rows > 0) {
|
||||
$out['Status'] = 'Success';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
7
pages/News/index.html
Normal file
7
pages/News/index.html
Normal file
@ -0,0 +1,7 @@
|
||||
<page minimal_permission_level="2" secret="no" page_title="Novinky"></page>
|
||||
<header>
|
||||
<h1 class="title"></h1>
|
||||
<p>Adlerka študentské news</p>
|
||||
<hr>
|
||||
</header>
|
||||
|
@ -1,8 +0,0 @@
|
||||
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
|
||||
<page minimal_permission_level="2" secret="yes" page_title="Súbory"></page>
|
||||
<div id="filelist"></div>
|
||||
<form id="uploadForm" enctype="multipart/form-data" method="POST">
|
||||
<label for="fileInput">Send this file: </label>
|
||||
<input name="userfile" type="file" id="fileInput" multiple />
|
||||
<input type="button" value="Send File" onclick="uploadFile()" />
|
||||
</form>
|
@ -1,47 +0,0 @@
|
||||
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
|
||||
<page minimal_permission_level="2" secret="yes" page_title="Prieskum"></page>
|
||||
<form id="surveyForm">
|
||||
<h3>Spokojnosť so stránkou:</h3>
|
||||
<input type="radio" name="satisfaction" value="5" id="spokojnost_super">
|
||||
<label for="spokojnost_super">Super</label>
|
||||
<input type="radio" name="satisfaction" value="4" id="spokojnost_dobre">
|
||||
<label for="spokojnost_dobre">Dobre</label>
|
||||
<input type="radio" name="satisfaction" value="3" id="spokojnost_da_sa">
|
||||
<label for="spokojnost_da_sa">Dá sa</label>
|
||||
<input type="radio" name="satisfaction" value="2" id="spokojnost_zle">
|
||||
<label for="spokojnost_zle">Zle</label>
|
||||
<input type="radio" name="satisfaction" value="1" id="spokojnost_nanic">
|
||||
<label for="spokojnost_nanic">Nanič</label>
|
||||
<br>
|
||||
<br>
|
||||
<h3>Funkčnosť stránky:</h3>
|
||||
<input type="radio" name="functionality" value="5" id="funkcnost_super">
|
||||
<label for="funkcnost_super">Super</label>
|
||||
<input type="radio" name="functionality" value="4" id="funkcnost_dobre">
|
||||
<label for="funkcnost_dobre">Dobre</label>
|
||||
<input type="radio" name="functionality" value="3" id="funkcnost_da_sa">
|
||||
<label for="funkcnost_da_sa">Dá sa</label>
|
||||
<input type="radio" name="functionality" value="2" id="funkcnost_zle">
|
||||
<label for="funkcnost_zle">Zle</label>
|
||||
<input type="radio" name="functionality" value="1" id="funkcnost_nanic">
|
||||
<label for="funkcnost_nanic">Nanič</label>
|
||||
<br>
|
||||
<br>
|
||||
<h3>Obsah stránky:</h3>
|
||||
<input type="radio" name="content" value="5" id="content_super">
|
||||
<label for="content_super">Super</label>
|
||||
<input type="radio" name="content" value="4" id="content_dobre">
|
||||
<label for="content_dobre">Dobre</label>
|
||||
<input type="radio" name="content" value="3" id="content_da_sa">
|
||||
<label for="content_da_sa">Dá sa</label>
|
||||
<input type="radio" name="content" value="2" id="content_zle">
|
||||
<label for="content_zle">Zle</label>
|
||||
<input type="radio" name="content" value="1" id="content_nanic">
|
||||
<label for="content_nanic">Nanič</label>
|
||||
<br>
|
||||
<br>
|
||||
<textarea name="comment" placeholder="Komentár" cols="80" rows="10" required></textarea>
|
||||
<br>
|
||||
<br>
|
||||
<button type="button" onclick="surveySubmit()">Odoslať</button>
|
||||
</form>
|
@ -17,6 +17,6 @@ return [
|
||||
[
|
||||
"minimal_permission_level" => 1,
|
||||
"secret" => "no",
|
||||
"page_title" => "Účet"
|
||||
"page_title" => "Account"
|
||||
]
|
||||
];
|
@ -1,7 +1,6 @@
|
||||
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
|
||||
<page minimal_permission_level="1" secret="no" page_title="Domov"></page>
|
||||
<header>
|
||||
<h1 class="title">Vitaj, na tejto úžasnej stránke</h1>
|
||||
<p>Neoficiálna študentská stránka pre Adlerku</p>
|
||||
</header>
|
||||
<hr>
|
||||
<hr>
|
||||
</header>
|
@ -16,6 +16,6 @@ return [
|
||||
[
|
||||
"minimal_permission_level" => 2,
|
||||
"secret" => "no",
|
||||
"page_title" => "Nastavenia"
|
||||
"page_title" => "Account"
|
||||
]
|
||||
];
|
6
pages/memes/index.html
Normal file
6
pages/memes/index.html
Normal file
@ -0,0 +1,6 @@
|
||||
<page minimal_permission_level="1" secret="no" page_title="Memečká"></page>
|
||||
<header>
|
||||
<h1 class="title">Adlerka Memes</h1>
|
||||
<p>Skoro ako <a href="https://reddit.com/r/adlerka" target="_blank">r/adlerka</a> - ale lepšie.</p>
|
||||
<hr>
|
||||
</header>
|
@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/router.php";
|
||||
require_once "lib/meme.php";
|
||||
|
||||
global $routerConfig;
|
||||
|
||||
$output = renderMemeGallery();
|
||||
|
||||
return [
|
||||
"output" => $output,
|
||||
"parameters" =>
|
||||
[
|
||||
"minimal_permission_level" => 1,
|
||||
"secret" => "no",
|
||||
"page_title" => "Galéria"
|
||||
]
|
||||
];
|
2
pages/memes/info.html
Normal file
2
pages/memes/info.html
Normal file
@ -0,0 +1,2 @@
|
||||
<page minimal_permission_level="1" secret="no" page_title="Memečká info"></page>
|
||||
<h1>Vitaj na oficiálnej stránke Memeov o AdlerkaSMP</h1>
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once "lib/router.php";
|
||||
require_once "lib/newsarticle.php";
|
||||
|
||||
global $routerConfig;
|
||||
|
||||
$output = file_get_contents($routerConfig["template_dir"] . "newsArticles.html");
|
||||
|
||||
$articles_out = "";
|
||||
$articles = [];
|
||||
$articles_tmp = getNewsArticles();
|
||||
if($articles_tmp['Status'] == "Success"){
|
||||
$articles = $articles_tmp["Articles"];
|
||||
}
|
||||
|
||||
$articleTemplate = file_get_contents($routerConfig["template_dir"] . "newsArticle.html");
|
||||
$output = str_replace("__TEMPLATE_FOR_ARTICLE_CONTENT__", $articleTemplate, $output);
|
||||
foreach ($articles as $article){
|
||||
$articleTitle = htmlspecialchars($article["Title"]);
|
||||
$articleBody = htmlspecialchars($article["Body"]);
|
||||
//$articleFileList = $article["FileList"];
|
||||
//$articleWrittenBy = $article["WrittenBy"];
|
||||
$articleWrittenAt = htmlspecialchars($article["WrittenAt"]);
|
||||
$articleWrittenByName = htmlspecialchars($article["WrittenByName"]);
|
||||
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_TITLE__", $articleTitle, $articleTemplate);
|
||||
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_AUTHOR__", $articleWrittenByName, $articleTemplate);
|
||||
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_DATE__", $articleWrittenAt, $articleTemplate);
|
||||
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_BODY__", $articleBody, $articleTemplate);
|
||||
|
||||
$articles_out .= $articleTemplate;
|
||||
}
|
||||
|
||||
$output = str_replace("__TEMPLATE__ARTICLES_HERE__", $articles_out, $output);
|
||||
|
||||
return [
|
||||
"output" => $output,
|
||||
"parameters" =>
|
||||
[
|
||||
"minimal_permission_level" => 1,
|
||||
"secret" => "no",
|
||||
"page_title" => "Novinky"
|
||||
]
|
||||
];
|
@ -1,8 +1,5 @@
|
||||
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
|
||||
<page minimal_permission_level="2" secret="no" page_title="Zošit"></page>
|
||||
<header>
|
||||
<h1 class="title">Adlerka Zošit</h1>
|
||||
</header>
|
||||
<hr>
|
||||
<h2>Čoskoro bude v prevádzke</h2>
|
||||
<h3>Nájdete(a pridáte) tu poznámky a úlohy zo školy</h3>
|
||||
<hr>
|
||||
</header>
|
@ -1,5 +0,0 @@
|
||||
<page minimal_permission_level="2" secret="no" page_title="INFO"></page>
|
||||
<header>
|
||||
<h1>Toto sú rozvrhy niektorých Adlerákov</h1>
|
||||
<h2>Zatiaľ hardcoded, potom dorobíme funkcionalitu zbierania dát z Edupage</h2>
|
||||
</header>
|
@ -1,62 +0,0 @@
|
||||
<page minimal_permission_level="2" secret="no" page_title="1.C 2.skupina"></page>
|
||||
<header>
|
||||
<h1>Rozvrh 1.C 2.Skupina</h1>
|
||||
</header>
|
||||
<main>
|
||||
<table class="rozvrh">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>0 (7:10 - 7:55)</th>
|
||||
<th>1 (8:00 - 8:45)</th>
|
||||
<th>2 (8:50 - 9:35)</th>
|
||||
<th>3 (9:45 - 10:30)</th>
|
||||
<th>4 (10:50 - 11:35)</th>
|
||||
<th>5 (11:45 - 12:30)</th>
|
||||
<th>6 (12:40 - 13:25)</th>
|
||||
<th>7 (13:30 - 14:15)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MAT</td>
|
||||
<td>ELK</td>
|
||||
<td>SJL</td>
|
||||
<td colspan="2">ZER</td>
|
||||
<td>ANJ</td>
|
||||
<td>MAT</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">PRX</td>
|
||||
<td>SJL</td>
|
||||
<td colspan="2">INF</td>
|
||||
<td>ELK</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>PRO</td>
|
||||
<td>SJL</td>
|
||||
<td>FYZ</td>
|
||||
<td>ANJ</td>
|
||||
<td>MAT</td>
|
||||
<td>ELK</td>
|
||||
<td>TSV</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
<td colspan="2">PRO</td>
|
||||
<td>ANJ</td>
|
||||
<td>TDK</td>
|
||||
<td>MAT</td>
|
||||
<td>ETV</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>TDK</td>
|
||||
<td>OBN</td>
|
||||
<td>TSV</td>
|
||||
<td>FYZ</td>
|
||||
<td>PRO</td>
|
||||
<td>DEJ</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
@ -1,62 +0,0 @@
|
||||
<page minimal_permission_level="2" secret="no" page_title="1.C 1.skupina"></page>
|
||||
<header>
|
||||
<h1>Rozvrh 1.C 1.Skupina</h1>
|
||||
</header>
|
||||
<main>
|
||||
<table class="rozvrh">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>0 (7:10 - 7:55)</th>
|
||||
<th>1 (8:00 - 8:45)</th>
|
||||
<th>2 (8:50 - 9:35)</th>
|
||||
<th>3 (9:45 - 10:30)</th>
|
||||
<th>4 (10:50 - 11:35)</th>
|
||||
<th>5 (11:45 - 12:30)</th>
|
||||
<th>6 (12:40 - 13:25)</th>
|
||||
<th>7 (13:30 - 14:15)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MAT</td>
|
||||
<td>ELK</td>
|
||||
<td>SJL</td>
|
||||
<td colspan="2">ZER</td>
|
||||
<td>MAT</td>
|
||||
<td>ANJ</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">PRX</td>
|
||||
<td>SJL</td>
|
||||
<td colspan="2">INF</td>
|
||||
<td>ELK</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>PRO</td>
|
||||
<td>SJL</td>
|
||||
<td>TSV</td>
|
||||
<td>FYZ</td>
|
||||
<td>ANJ</td>
|
||||
<td>MAT</td>
|
||||
<td>ELK</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
<td colspan="2">PRO</td>
|
||||
<td>TDK</td>
|
||||
<td>TSV</td>
|
||||
<td>MAT</td>
|
||||
<td>ETV</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ANJ</td>
|
||||
<td>OBN</td>
|
||||
<td>TDK</td>
|
||||
<td>FYZ</td>
|
||||
<td>PRO</td>
|
||||
<td>DEJ</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
@ -1,64 +0,0 @@
|
||||
<page minimal_permission_level="2" secret="no" page_title="1.D 1.skupina"></page>
|
||||
|
||||
<header>
|
||||
<h1>Rozvrh 1.D 1.Skupina</h1>
|
||||
</header>
|
||||
<main>
|
||||
<table class="rozvrh">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>0 (7:10 - 7:55)</th>
|
||||
<th>1 (8:00 - 8:45)</th>
|
||||
<th>2 (8:50 - 9:35)</th>
|
||||
<th>3 (9:45 - 10:30)</th>
|
||||
<th>4 (10:50 - 11:35)</th>
|
||||
<th>5 (11:45 - 12:30)</th>
|
||||
<th>6 (12:40 - 13:25)</th>
|
||||
<th>7 (13:30 - 14:15)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>TSV</td>
|
||||
<td colspan="2">PRO</td>
|
||||
<td>MAT</td>
|
||||
<td>TDK</td>
|
||||
<td>PRO</td>
|
||||
<td>SJL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>FYZ</td>
|
||||
<td>TDK</td>
|
||||
<td>MAT</td>
|
||||
<td>ELK</td>
|
||||
<td>OBN</td>
|
||||
<td>ANJ</td>
|
||||
<td>ETV</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ELK</td>
|
||||
<td>MAT</td>
|
||||
<td>SJL</td>
|
||||
<td>ANJ</td>
|
||||
<td>ELK</td>
|
||||
<td colspan="2">INF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
<td colspan="2">ZER</td>
|
||||
<td>MAT</td>
|
||||
<td>FYZ</td>
|
||||
<td>DEJ</td>
|
||||
<td>TSV</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SJL</td>
|
||||
<td>ANJ</td>
|
||||
<td>PRO</td>
|
||||
<td colspan="3">PRX</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
@ -1,33 +1,35 @@
|
||||
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
|
||||
<page minimal_permission_level="1" secret="no" page_title="Domov"></page>
|
||||
<header>
|
||||
<h1 class="title">Vitaj, na oficiálnej AdlerkaSMP stránke</h1>
|
||||
<p>Najlepší <a href="https://minecraft.net" style="text-decoration: underline; color: #fff;" target="_blank">Minecraft®™</a>
|
||||
server na Adlerke</p>
|
||||
</header>
|
||||
<hr>
|
||||
<main>
|
||||
<h1 class="title">Vitaj na oficiálnej AdlerkaSMP stránke</h1>
|
||||
<p>Najlepší <a href="https://minecraft.net" style="text-decoration: underline; color: #fff;" target="_blank">Minecraft®™</a> server na Adlerke</p>
|
||||
<hr>
|
||||
<div class="wrapper feature-list">
|
||||
<h2>Čo môžeš očakávať od AdlerkaSMP:</h2>
|
||||
|
||||
<ul class="feature-list-ul">
|
||||
<li>Módovaný gameplay bez nutnosti sťahovať módy (niektoré módy však vylepšia zážitok)</li>
|
||||
<ul class="feature-list-ul">
|
||||
<li>
|
||||
<strong>Vlastné pluginy:</strong>
|
||||
<ul>
|
||||
<li>Plugin 1</li>
|
||||
<li>Plugin 2</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>Jednoduché pripojenie, hostname je <strong>adlerka.top</strong></li>
|
||||
<li>
|
||||
<strong>Super admini:</strong>
|
||||
<ul>
|
||||
<li>Starajú sa o server</li>
|
||||
<li>Udržiavajú ho bezpečným miestom</li>
|
||||
<li>
|
||||
<strong>Zoznam adminov:</strong>
|
||||
<ul>
|
||||
<li>BRNSystems</li>
|
||||
<li>YeahAkis_</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<strong>Kvalitné pluginy:</strong>
|
||||
<ul>
|
||||
<li>Oraxen</li>
|
||||
<li>ModelManager</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Super admini:</strong>
|
||||
<ul>
|
||||
<li>Robíme celý server.</li>
|
||||
<li>Vďaka nám je AdlerkaSMP<br>bezpečné miesto.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
</header>
|
2
pages/smp/info.html
Normal file
2
pages/smp/info.html
Normal file
@ -0,0 +1,2 @@
|
||||
<page minimal_permission_level="1" secret="no" page_title="AdlerkaSMP info"></page>
|
||||
<h1>Vitaj na oficiálnej stránke Informácii o AdlerkaSMP</h1>
|
@ -1,61 +0,0 @@
|
||||
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
|
||||
<page minimal_permission_level="1" secret="no" page_title="Modlist"></page>
|
||||
<main>
|
||||
<h2>Launcher:</h2>
|
||||
<a href="https://prismlauncher.org/download/">Prism Launcher <b>(ODPORÚČANÝ)</b></a><br>
|
||||
<h2>Módy pre klienta:</h2>
|
||||
<a href="/assets/adlerka_client.mrpack">Komplet ako MrPack <b>(ODPORÚČANÉ)</b></a><br>
|
||||
<h3>Vizuálne:</h3>
|
||||
<ol>
|
||||
<li><a href="https://cdn.modrinth.com/data/zV5r3pPn/versions/kJmEO0xO/skinlayers3d-fabric-1.6.2-mc1.20.4.jar">3D Skin Layers</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/wdLuzzEP/versions/rNWb9ncY/Gamma-Utils-1.7.19-mc1.20.4.jar">Gamma Utils</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/w7ThoJFB/versions/BalILUb7/Zoomify-2.13.2.jar">Zoomify</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/M08ruV16/versions/jGGumR4a/bobby-5.1.0%2Bmc1.20.4.jar">Bobby</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/1IjD5062/versions/JXhQlDZl/continuity-3.0.0-beta.4%2B1.20.2.jar">Continuity</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/ZcR9weSm/versions/UrOG4IKT/dynamiccrosshair-7.7%2B1.20.4-fabric.jar">Dynamic Crosshair</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/rUgZvGzi/versions/AqXSvu6M/eating-animation-1.20%2B1.9.61.jar">Eating Animation</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/WhbRG4iK/versions/PNG0Dp43/fallingleaves-1.15.5%2B1.20.1.jar">Falling Leaves</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/YL57xq9U/versions/kGdJ11Rt/iris-mc1.20.4-1.6.17.jar">Iris</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/yBW8D80W/versions/mrQ8ZiyU/lambdynamiclights-2.3.4%2B1.20.4.jar">Lambdynamiclights</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/MPCX6s5C/versions/ZLjUeuU8/notenoughanimations-fabric-1.7.1-mc1.20.4.jar">Not Enough Animations</a></li>
|
||||
</ol>
|
||||
<h3>Performance:</h3>
|
||||
<ol>
|
||||
<li><a href="https://cdn.modrinth.com/data/NNAgCjsB/versions/7JR5qJ8f/entityculling-fabric-1.6.4-mc1.20.4.jar">Entity Culling</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/uXXizFIs/versions/pguEMpy9/ferritecore-6.0.3-fabric.jar">FerriteCore</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/Orvt0mRa/versions/Aouse6P7/indium-1.0.30%2Bmc1.20.4.jar">Indium</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/fQEb0iXm/versions/bRcuOnao/krypton-0.2.6.jar">Krypton</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/gvQqBUqZ/versions/nMhjKWVE/lithium-fabric-mc1.20.4-0.12.1.jar">Lithium</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/Bh37bMuy/versions/fkLiGoHs/reeses_sodium_options-1.7.2%2Bmc1.20.4-build.102.jar">Reese's Sodium Options</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/AANobbMI/versions/4GyXKCLd/sodium-fabric-0.5.8%2Bmc1.20.4.jar">Sodium</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/PtjYWJkn/versions/M0ndiav7/sodium-extra-0.5.4%2Bmc1.20.4-build.116.jar">Sodium-extra</a></li>
|
||||
</ol>
|
||||
<h3>Utility:</h3>
|
||||
<ol>
|
||||
<li><a href="https://cdn.modrinth.com/data/DFqQfIBR/versions/rGQZ2yBQ/CraftPresence-2.3.5%2B1.20.4.jar">CraftPresence</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/8shC1gFX/versions/AkivIlyi/BetterF3-9.0.2-Fabric-1.20.4.jar">BetterF3</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/nvQzSEkH/versions/fNHCa6bl/Jade-1.20.4-fabric-13.3.1.jar">Jade</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/aC3cM3Vq/versions/m0Dd8Cjy/MouseTweaks-fabric-mc1.20-2.25.jar">Mouse Tweaks</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/qQyHxfxd/versions/tfv6A4l5/NoChatReports-FABRIC-1.20.4-v2.5.0.jar">No Chat Reports</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/nfn13YXA/versions/Jhw0fDTs/RoughlyEnoughItems-14.0.688-fabric.jar">Roughly Enough Items</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/V8XJ8f5f/versions/q9eTEsvC/RoughlyEnoughProfessions-fabric-1.20.4-2.2.0.jar">Roughly Enough Professions</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/NcUtCpym/versions/2lbtkEPK/XaerosWorldMap_1.38.1_Fabric_1.20.4.jar">Xaero's WorldMap</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/1bokaNcj/versions/N5jBKzC0/Xaeros_Minimap_24.0.3_Fabric_1.20.4.jar">Xaero's_Minimap</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/EsAfCjCV/versions/pmFyu3Sz/appleskin-fabric-mc1.20.3-2.5.1.jar">Appleskin</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/mOgUt4GM/versions/sjtVVlsA/modmenu-9.0.0.jar">Modmenu</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/Nv2fQJo5/versions/TGJXKoTQ/replaymod-1.20.4-2.6.15.jar">Replaymod</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/njGhQ4fN/versions/4tIwORrJ/roughly-searchable-2.6.1%2B1.20.4.jar">Roughly Searchable</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/9eGKb6K1/versions/r7e564VW/voicechat-fabric-1.20.4-2.5.11.jar">Simple Voice Chat</a></li>
|
||||
</ol>
|
||||
<h3>Dependecies:</h3>
|
||||
<ol>
|
||||
<li><a href="https://cdn.modrinth.com/data/lhGA9TYQ/versions/kVjQWX0l/architectury-11.1.17-fabric.jar">Architectury</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/gu7yAYhd/versions/augSR8tA/cc-tweaked-1.20.4-fabric-1.110.0.jar">CC Tweaked</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/9s6osm5g/versions/eBZiZ9NS/cloth-config-13.0.121-fabric.jar">Cloth Config</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/P7dR8mSH/versions/htRy7kbI/fabric-api-0.96.11%2B1.20.4.jar">Fabric Api</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/Ha28R6CL/versions/ZMokinzs/fabric-language-kotlin-1.10.19%2Bkotlin.1.9.23.jar">Fabric Language Kotlin</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/hvFnDODi/versions/0.1.3/lazydfu-0.1.3.jar">Lazydfu</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/xGdtZczs/versions/Kk7rWLSf/polymer-bundled-0.7.7%2B1.20.4.jar">Polymer</a></li>
|
||||
<li><a href="https://cdn.modrinth.com/data/1eAoo2KR/versions/StXMrAsz/yet-another-config-lib-fabric-3.3.2%2B1.20.4.jar">Yet Another Config Lib</a></li>
|
||||
</ol>
|
||||
</main>
|
@ -2,6 +2,5 @@
|
||||
<h2>TY KÁR KAM TO DEŠ</h2>
|
||||
<h1 class="error-code">403</h1>
|
||||
<h3><i class="ri-error-warning-line"></i> Našli sme stránku ktorú hľadáš, ale nemáš práva na ňu pristupovať: <span class="error">__TEMPLATE_PAGE_TITLE__</span>. <i class="ri-error-warning-line"></i></h3>
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<a href="__DEFAULT_LINK__" role="button" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
|
||||
<a href="/index" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
|
||||
</div>
|
@ -2,6 +2,5 @@
|
||||
<h2>TY KÁR KAM TO DEŠ</h2>
|
||||
<h1 class="error-code">404</h1>
|
||||
<h3><i class="ri-error-warning-line"></i> Nenašli sme stránku ktorú hľadáš: <span class="error">__TEMPLATE_PAGE_TITLE__</span>. <i class="ri-error-warning-line"></i></h3>
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<a href="__DEFAULT_LINK__" role="button" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
|
||||
<a href="/index" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
|
||||
</div>
|
@ -2,6 +2,5 @@
|
||||
<h2>Niekto to pobabral</h2>
|
||||
<h1 class="error-code">500</h1>
|
||||
<h3><i class="ri-error-warning-line"></i> Nejaký neschopný vývojár nevedel robiť túto stránku. <i class="ri-error-warning-line"></i></h3>
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<a href="__DEFAULT_LINK__" role="button" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
|
||||
<a href="/index" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
|
||||
</div>
|
@ -1,25 +1,85 @@
|
||||
<div id="admin-settings">
|
||||
<div class="form-container" id="addActivationCodesForm">
|
||||
<h1>Activation Codes</h1>
|
||||
<script>
|
||||
async function addActivationCodes() {
|
||||
const count = document.getElementById("activationCodeCount").value;
|
||||
|
||||
<h2>List Activation Codes</h2>
|
||||
<button type="button" onclick="listActivationCodes()">List Activation Codes</button><br>
|
||||
<h2>Add Activation Codes</h2>
|
||||
<label for="activationCodeCount">Activation Code Count:</label>
|
||||
<input type="text" id="activationCodeCount" name="activationCodeCount" value="1" required><br>
|
||||
<button type="button" onclick="addActivationCodes()">Add Activation Codes</button>
|
||||
<br>
|
||||
<table id="codeListTable"></table>
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "add_activation_codes");
|
||||
data.append("count", count);
|
||||
|
||||
</div>
|
||||
const result = await doAccountAction(data, "Activation codes added Successfully!", "Activation codes addition failed.");
|
||||
|
||||
<hr>
|
||||
displayList(result.ActivationCodes, "codeListTable", deleteActivationCode);
|
||||
}
|
||||
|
||||
async function listUsers() {
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "list_users");
|
||||
|
||||
const result = await doAccountAction(data, "User list retrieved Successfully!", "User list retrieval failed.");
|
||||
|
||||
if (result && result.Status === "Success") {
|
||||
displayList(result.Users, "userListTable", deleteUser);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function listActivationCodes() {
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "list_activation_codes");
|
||||
|
||||
const result = await doAccountAction(data, "Activation code list retrieved Successfully!", "Activation code list retrieval failed.");
|
||||
|
||||
displayList(result.ActivationCodes, "codeListTable", deleteActivationCode);
|
||||
}
|
||||
|
||||
function deleteUser(userId) {
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "delete_user");
|
||||
data.append("user_id", userId);
|
||||
|
||||
doAccountAction(data, "User deleted Successfully!", "User deletion failed.");
|
||||
listUsers();
|
||||
}
|
||||
|
||||
function deleteActivationCode(activationCode) {
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "delete_activation_code");
|
||||
data.append("activation_code", activationCode);
|
||||
|
||||
doAccountAction(data, "Activation code deleted Successfully!", "Activation code deletion failed.");
|
||||
listActivationCodes();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script defer>
|
||||
listActivationCodes();
|
||||
listUsers();
|
||||
</script>
|
||||
|
||||
<div class="form-container" id="addActivationCodesForm">
|
||||
<h1>Activation Codes</h1>
|
||||
|
||||
<h2>List Activation Codes</h2>
|
||||
<button type="button" onclick="listActivationCodes()">List Activation Codes</button><br>
|
||||
<h2>Add Activation Codes</h2>
|
||||
<label for="activationCodeCount">Activation Code Count:</label>
|
||||
<input type="text" id="activationCodeCount" name="activationCodeCount" value="1" required><br>
|
||||
<button type="button" onclick="addActivationCodes()">Add Activation Codes</button>
|
||||
<br>
|
||||
<table id="codeListTable"></table>
|
||||
|
||||
<div class="form-container" id="listUsersForm">
|
||||
<h1>List Users</h1>
|
||||
<button type="button" onclick="listUsers()">List Users</button><br>
|
||||
<br>
|
||||
<table id="userListTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="form-container" id="listUsersForm">
|
||||
<h1>List Users</h1>
|
||||
<button type="button" onclick="listUsers()">List Users</button><br>
|
||||
<br>
|
||||
<table id="userListTable"></table>
|
||||
</div>
|
||||
|
||||
<hr>
|
@ -1,5 +1,19 @@
|
||||
<script defer>
|
||||
async function getUserInfo() {
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "get_user_info");
|
||||
|
||||
const result = await doAccountAction(data, null, null, true);
|
||||
|
||||
if (result && result.Status === "Success") {
|
||||
document.getElementById("welcomeMsg").innerText = `Ahoj, ${result.UserInfo.FirstName}.`;
|
||||
}
|
||||
}
|
||||
|
||||
getUserInfo();
|
||||
</script>
|
||||
|
||||
|
||||
<div class="dashboard">
|
||||
<header>
|
||||
<h1 id="welcomeMsg"></h1>
|
||||
</header>
|
||||
<h1 id="welcomeMsg"></h1>
|
||||
</div>
|
@ -1 +0,0 @@
|
||||
<p>Toto nie je oficiálna stránka <span id="ye-span">Adlerky</span>, jedná sa o neoficiálnu študentskú stránku</p>
|
@ -1,28 +1,77 @@
|
||||
<script>
|
||||
function login() {
|
||||
const email = document.getElementById("login_email").value;
|
||||
const password = document.getElementById("login_password").value;
|
||||
doLogin(email, password);
|
||||
}
|
||||
|
||||
function doLogin(email, password) {
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "login");
|
||||
data.append("email", email);
|
||||
data.append("password", password);
|
||||
|
||||
doAccountAction(data, "Login Successful!", "Login failed. Please check your credentials.");
|
||||
}
|
||||
|
||||
function register() {
|
||||
const firstName = document.getElementById("register_firstName").value;
|
||||
const lastName = document.getElementById("register_lastName").value;
|
||||
const email = document.getElementById("register_email").value;
|
||||
const password = document.getElementById("register_password").value;
|
||||
const activationToken = document.getElementById("register_activationToken").value;
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "register");
|
||||
data.append("firstname", firstName);
|
||||
data.append("lastname", lastName);
|
||||
data.append("email", email);
|
||||
data.append("password", password);
|
||||
data.append("activation_token", activationToken);
|
||||
|
||||
doRegister(data);
|
||||
}
|
||||
|
||||
function doRegister(requestData) {
|
||||
doAccountAction(requestData, "Registration Successful!", "Registration failed.");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Centralized Status Message -->
|
||||
<p id="StatusMessage"></p>
|
||||
<main class="login-file">
|
||||
<div class="container" id="container">
|
||||
<div class="form-container sign-in" id="sign_in_form">
|
||||
<h1>Login</h1>
|
||||
<form class="form-content sign-in">
|
||||
<input type="email" name="email" id="login_email" required placeholder="Email">
|
||||
<input type="password" name="password" id="login_password" required placeholder="Password">
|
||||
<button type="button" onclick="login()">Login</button>
|
||||
</form>
|
||||
<p onclick="toggleRegister()">Don't have an account</p>
|
||||
</div>
|
||||
<div class="form-container sign-up hidden" id="sign_up_form">
|
||||
<h1>Create Account</h1>
|
||||
<form class="form-content sign-up">
|
||||
<input type="text" name="firstName" id="register_firstName" required placeholder="First name">
|
||||
<input type="text" name="lastName" id="register_lastName" required placeholder="Last name">
|
||||
<input type="email" name="email" id="register_email" required placeholder="Email">
|
||||
<input type="password" name="password" id="register_password" required placeholder="Password">
|
||||
<input type="text" name="activationToken" id="register_activationToken" required
|
||||
placeholder="Activation Token">
|
||||
<button type="button">Register</button>
|
||||
</form>
|
||||
<p onclick="toggleRegister()">Already have an account</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="form-container" id="loginForm">
|
||||
<h1>Login</h1>
|
||||
<form>
|
||||
<label for="login_email">Email:</label>
|
||||
<input type="email" id="login_email" name="email" required><br>
|
||||
|
||||
<label for="login_password">Password:</label>
|
||||
<input type="password" id="login_password" name="password" required><br>
|
||||
|
||||
<button type="button" onclick="login()">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="form-container" id="register_Form">
|
||||
<h1>Register</h1>
|
||||
<form>
|
||||
<label for="register_firstName">First Name:</label>
|
||||
<input type="text" id="register_firstName" name="firstName" required><br>
|
||||
|
||||
<label for="register_lastName">Last Name:</label>
|
||||
<input type="text" id="register_lastName" name="lastName" required><br>
|
||||
|
||||
<label for="register_email">Email:</label>
|
||||
<input type="email" id="register_email" name="email" required><br>
|
||||
|
||||
<label for="register_password">Password:</label>
|
||||
<input type="password" id="register_password" name="password" required><br>
|
||||
|
||||
<label for="register_activationToken">Activation Token:</label>
|
||||
<input type="text" id="register_activationToken" name="activationToken" required><br>
|
||||
|
||||
<button type="button" onclick="register()">Register</button>
|
||||
</form>
|
||||
</div>
|
@ -1,27 +0,0 @@
|
||||
<article class="meme" id="meme___TEMPLATE_MEME_ID__">
|
||||
<div class="meme_header" id="meme_header___TEMPLATE_MEME_ID__">
|
||||
<h2 class='meme_title' id="meme_title___TEMPLATE_MEME_ID__">__TEMPLATE_MEME_TITLE__</h2>
|
||||
<div class="meme_topbar" id="meme_info___TEMPLATE_MEME_ID__">
|
||||
<div class="meme_voting" id="meme_voting___TEMPLATE_MEME_ID__">
|
||||
__TEMPLATE_MEME_UPVOTE__
|
||||
<p class="votes_counter __TEMPLATE_MEME_VOTE_COUNTER_CLASS__"
|
||||
id="meme_votes_counter___TEMPLATE_MEME_ID__">__TEMPLATE_MEME_VOTES_NUMBER__</p>
|
||||
__TEMPLATE_MEME_DOWNVOTE__
|
||||
</div>
|
||||
<div class="meme_info">
|
||||
<p class='meme_author' id="meme_author___TEMPLATE_MEME_ID__"><i class="ri-user-line"></i>__TEMPLATE_MEME_AUTHOR__
|
||||
</p>
|
||||
<p class='meme_date' id="meme_date___TEMPLATE_MEME_ID__"><i class="ri-calendar-line"></i>__TEMPLATE_MEME_DATE__
|
||||
</p>
|
||||
__TEMPLATE_MEME_DELETE_BUTTON__
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meme_body" id="meme_body___TEMPLATE_MEME_ID__">
|
||||
<a class="meme_link" href="__TEMPLATE_MEME_IMAGE__" download>
|
||||
<img id="meme_image___TEMPLATE_MEME_ID__" src="__TEMPLATE_MEME_IMAGE__" width="__TEMPLATE_MEME_IMAGE_WIDTH__"
|
||||
height="__TEMPLATE_MEME_IMAGE_HEIGHT__" alt="meme image"
|
||||
class="meme_image"></a>
|
||||
<p class="meme_text" id="meme_text___TEMPLATE_MEME_ID__">__TEMPLATE_MEME_TEXT__</p>
|
||||
</div>
|
||||
</article>
|
@ -1,15 +0,0 @@
|
||||
<div id="memecreatecontainer" class="hidden">
|
||||
<div id="memecreate">
|
||||
<label for="meme_title_input">Názov meme-u: </label>
|
||||
<input type="text" id="meme_title_input" placeholder="Názov meme-u"/>
|
||||
|
||||
<label for="meme_text_input">Text meme-u: </label>
|
||||
<input type="text" id="meme_text_input" placeholder="Text meme-u"/>
|
||||
|
||||
<label for="meme_image_input">Obrázok meme-u</label>
|
||||
<select id="meme_image_input"></select>
|
||||
|
||||
<button id="memecreatebutton" onclick="addMeme()"><i class="ri-add-circle-line"></i></button>
|
||||
<button id="memecreateclose" onclick="togglememecreate()"><i class="ri-close-line"></i></button>
|
||||
</div>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
<header>
|
||||
<h1 class="title">Adlerka Memes</h1>
|
||||
<p>Skoro, ako <a href="https://reddit.com/r/adlerka" target="_blank">r/adlerka</a>, ale lepšie.</p>
|
||||
<button id="memecreateopen" onclick="togglememecreate()"><i class="ri-add-circle-line"></i></button>
|
||||
<hr>
|
||||
</header>
|
||||
<main>
|
||||
<div id="meme_gallery">
|
||||
__TEMPLATE_MEMES_HERE__
|
||||
</div>
|
||||
__TEMPLATE_MEME_ADD__
|
||||
</main>
|
@ -1,13 +1,6 @@
|
||||
<div class="logo">
|
||||
<a href="/home/index">
|
||||
<picture id="standard-logo">
|
||||
<source media="(min-width:4200px)" srcset="/assets/images/adlerka_256.png">
|
||||
<source media="(min-width:2100px)" srcset="/assets/images/adlerka_128.png">
|
||||
<img src="/assets/images/adlerka_64.png" alt="Adlerka logo" style="width:auto;">
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
<i class="ri-menu-line" id="toggle_button"></i>
|
||||
<ul id="navsite_list">
|
||||
__NAV_PAGES__
|
||||
</ul>
|
||||
<nav>
|
||||
<div class="logo"><img alt="Adlerka logo" class="standard-logo" src="https://www.adlerka.sk/wp-content/uploads/2021/09/Logo_text_Adlerka_modro_cerveno_biele-e1652431356820.png" title="Adlerka"></div>
|
||||
<ul class="navsite_list">
|
||||
__NAV_PAGES__
|
||||
</ul>
|
||||
</nav>
|
@ -1,11 +0,0 @@
|
||||
<article>
|
||||
<h2 class='newstitle'>__TEMPLATE_ARTICLE_TITLE__</h2>
|
||||
<div class="articleinfo">
|
||||
<p class='newsauthor'><i class="ri-user-line"></i>__TEMPLATE_ARTICLE_AUTHOR__</p>
|
||||
<p class='newsdate'><i class="ri-calendar-line"></i>__TEMPLATE_ARTICLE_DATE__</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='newsbody'>
|
||||
__TEMPLATE_ARTICLE_BODY__
|
||||
</div>
|
||||
</article>
|
@ -1,24 +0,0 @@
|
||||
<header>
|
||||
<h1 class="title"></h1>
|
||||
<p>Adlerka študentské news</p>
|
||||
<button id="articlecreateopen" onclick="togglearticlecreate()"><i class="ri-add-circle-line"></i></button>
|
||||
</header>
|
||||
|
||||
<template data-template-name="article">
|
||||
__TEMPLATE_FOR_ARTICLE_CONTENT__
|
||||
</template>
|
||||
|
||||
<div id="articleslist">
|
||||
__TEMPLATE__ARTICLES_HERE__
|
||||
</div>
|
||||
|
||||
<div id="articlecreatecontainer" class="hidden">
|
||||
<div id="articlecreate">
|
||||
<input type="text" placeholder="Article Title" id="articletitleinput"><br>
|
||||
<textarea id="articlebodyinput" placeholder="Article Body" rows="10" cols="80"></textarea><br>
|
||||
<label for="articleprivilegeinput">Oprávnenie na pozretie článku:</label>
|
||||
<input type="number" id="articleprivilegeinput" min="1" value="1" step="1">
|
||||
<button id="articlesubmit" onclick="submitarticle()"><i class="ri-add-circle-line"></i></button>
|
||||
<button id="articlecreateclose" onclick="togglearticlecreate()"><i class="ri-close-line"></i></button>
|
||||
</div>
|
||||
</div>
|
@ -1,29 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="sk" data-theme="dark">
|
||||
<html lang="sk">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/3rdparty/fonts/remixicon/remixicon.css">
|
||||
<link rel="stylesheet" href="/assets/3rdparty/pico.min.css">
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
|
||||
__TEMPLATE__DYNAMIC__SCRIPT__
|
||||
__TEMPLATE__DYNAMIC__STYLE__
|
||||
<script src="/assets/script.js"></script>
|
||||
<script async src="https://umami.brn.systems/script.js" data-website-id="95e93885-5c19-4cab-ba9b-2f746a316a2a"></script>
|
||||
<script async src="/assets/script.js"></script>
|
||||
<title>__TEMPLATE_PAGE_TITLE__</title>
|
||||
__TEMPLATE_SEO_STUFF__
|
||||
<title>Adlerka __TEMPLATE_PAGE_TITLE__</title>
|
||||
__TEMPLATE__DYNASTYLE__
|
||||
</head>
|
||||
<body>
|
||||
<nav id="navbar_container">
|
||||
__TEMPLATE__NAV__
|
||||
</nav>
|
||||
<div id="statusMessageContainer"></div>
|
||||
<main id="page_container">
|
||||
__TEMPLATE__PAGE__
|
||||
</main>
|
||||
<footer id="footer_container">
|
||||
__TEMPLATE__FOOTER__
|
||||
</footer>
|
||||
__TEMPLATE__NAV__
|
||||
__TEMPLATE__PAGE__
|
||||
</body>
|
||||
</html>
|
@ -1,50 +1,114 @@
|
||||
<script>
|
||||
function changePassword() {
|
||||
const oldPassword = document.getElementById("changeOldPassword").value;
|
||||
const newPassword = document.getElementById("changeNewPassword").value;
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "change_password");
|
||||
data.append("old_password", oldPassword);
|
||||
data.append("new_password", newPassword);
|
||||
|
||||
doChangePassword(data, "Password change Successful!", "Password change failed.");
|
||||
}
|
||||
|
||||
function doChangePassword(requestData, successMessage, failureMessage) {
|
||||
doAccountAction(requestData, successMessage, failureMessage);
|
||||
}
|
||||
|
||||
function updateUserProfile() {
|
||||
const firstName = document.getElementById("updateFirstName").value;
|
||||
const lastName = document.getElementById("updateLastName").value;
|
||||
const nickname = document.getElementById("updateNickname").value;
|
||||
const minecraftNick = document.getElementById("updateMinecraftNick").value;
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "update_user_profile");
|
||||
data.append("first_name", firstName);
|
||||
data.append("last_name", lastName);
|
||||
data.append("nickname", nickname);
|
||||
data.append("minecraft_nick", minecraftNick);
|
||||
|
||||
doAccountAction(data, "Profile update Successful!", "Profile update failed.");
|
||||
}
|
||||
|
||||
function updateEmail() {
|
||||
const newEmail = document.getElementById("updateNewEmail").value;
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "update_user_email");
|
||||
data.append("email", newEmail);
|
||||
|
||||
doAccountAction(data, "Email update Successful!", "Email update failed.");
|
||||
}
|
||||
|
||||
function populateUserInfoFields(userData) {
|
||||
document.getElementById("updateFirstName").value = userData.FirstName || "";
|
||||
document.getElementById("updateLastName").value = userData.LastName || "";
|
||||
document.getElementById("updateNickname").value = userData.Nickname || "";
|
||||
document.getElementById("updateMinecraftNick").value = userData.MinecraftNick || "";
|
||||
document.getElementById("updateNewEmail").value = userData.Email || "";
|
||||
}
|
||||
|
||||
async function getUserInfo() {
|
||||
const data = new URLSearchParams();
|
||||
data.append("action", "get_user_info");
|
||||
|
||||
const result = await doAccountAction(data, "User info retrieved Successfully!", "User info retrieval failed.", true);
|
||||
|
||||
if (result && result.Status === "Success") {
|
||||
populateUserInfoFields(result.UserInfo);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script defer>
|
||||
getUserInfo();
|
||||
</script>
|
||||
|
||||
<!-- Centralized Status Message -->
|
||||
<p id="StatusMessage"></p>
|
||||
<div id="user-settings">
|
||||
<button type="button" onclick="logout()">Logout</button>
|
||||
<br>
|
||||
|
||||
<div class="form-container" id="updateUserProfileForm">
|
||||
<h1>Update User</h1>
|
||||
<button type="button" onclick="logout()">Logout</button><br>
|
||||
|
||||
<h2>Profile</h2>
|
||||
<div class="form-content" id="profile-form">
|
||||
<label for="updateFirstName">First Name:</label>
|
||||
<input type="text" id="updateFirstName" name="updateFirstName" required><br>
|
||||
<div class="form-container" id="updateUserProfileForm">
|
||||
<h1>Update User</h1>
|
||||
|
||||
<label for="updateLastName">Last Name:</label>
|
||||
<input type="text" id="updateLastName" name="updateLastName" required><br>
|
||||
<h2>Profile</h2>
|
||||
<label for="updateFirstName">First Name:</label>
|
||||
<input type="text" id="updateFirstName" name="updateFirstName" required><br>
|
||||
|
||||
<label for="updateNickname">Nickname:</label>
|
||||
<input type="text" id="updateNickname" name="updateNickname" required><br>
|
||||
<label for="updateLastName">Last Name:</label>
|
||||
<input type="text" id="updateLastName" name="updateLastName" required><br>
|
||||
|
||||
<label for="updateMinecraftNick">Minecraft Nick:</label>
|
||||
<input type="text" id="updateMinecraftNick" name="updateMinecraftNick" required><br>
|
||||
<label for="updateNickname">Nickname:</label>
|
||||
<input type="text" id="updateNickname" name="updateNickname" required><br>
|
||||
|
||||
<button type="button" onclick="updateUserProfile()">Update Profile</button>
|
||||
</div>
|
||||
<br><br>
|
||||
<label for="updateMinecraftNick">Minecraft Nick:</label>
|
||||
<input type="text" id="updateMinecraftNick" name="updateMinecraftNick" required><br>
|
||||
|
||||
<h2>Email</h2>
|
||||
<div class="form-content" id="email-form">
|
||||
<label for="updateNewEmail">New Email:</label>
|
||||
<input type="email" id="updateNewEmail" name="updateNewEmail" required><br>
|
||||
<button type="button" onclick="updateUserProfile()">Update Profile</button>
|
||||
|
||||
<button type="button" onclick="updateEmail()">Update Email</button>
|
||||
</div>
|
||||
<br><br>
|
||||
<br><br>
|
||||
|
||||
<h2>Password</h2>
|
||||
<div class="form-content" id="password-form">
|
||||
<label for="changeOldPassword">Old Password:</label>
|
||||
<input type="password" id="changeOldPassword" name="changeOldPassword" required><br>
|
||||
<h2>Email</h2>
|
||||
|
||||
<label for="changeNewPassword">New Password:</label>
|
||||
<input type="password" id="changeNewPassword" name="changeNewPassword" required><br>
|
||||
<label for="updateNewEmail">New Email:</label>
|
||||
<input type="email" id="updateNewEmail" name="updateNewEmail" required><br>
|
||||
|
||||
<button type="button" onclick="changePassword()">Change Password</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onclick="updateEmail()">Update Email</button>
|
||||
|
||||
<br><br>
|
||||
|
||||
<h2>Password</h2>
|
||||
|
||||
<label for="changeOldPassword">Old Password:</label>
|
||||
<input type="password" id="changeOldPassword" name="changeOldPassword" required><br>
|
||||
|
||||
<label for="changeNewPassword">New Password:</label>
|
||||
<input type="password" id="changeNewPassword" name="changeNewPassword" required><br>
|
||||
|
||||
<button type="button" onclick="changePassword()">Change Password</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user