Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

45 changed files with 304 additions and 2548 deletions

File diff suppressed because one or more lines are too long

@ -1,595 +0,0 @@
(() => {
"use strict";
(() => {
function getDom(t) {
return function hasDom(t) {
return null !== document.querySelector(t)
}(t) ? document.querySelector(t) : null
}
function setDom(t) {
for (var e, n = document.createElement(t), o = arguments.length, i = new Array(o > 1 ? o - 1 : 0), r = 1; r < o; r++) i[r - 1] = arguments[r];
return (e = n.classList).add.apply(e, i), n
}
function findDom(t, e) {
try {
return t.querySelector(e)
} catch (t) {
return null
}
}
var t = "sliderm", e = "sliderm__slides", n = "sliderm__paginations", o = "sliderm__pagination",
i = "sliderm__slide--clone";
function isInteger(t) {
return Number.isFinite(t)
}
var r = function bold(t) {
var e = {1: "thin", 2: "regular", 3: "bold"};
return void 0 !== e[t] ? e[t] : "regular"
}, a = function shape(t) {
return "none" === t || "square" === t ? t : "circle"
}, s = function size(t) {
return isInteger(t) && 16 !== t ? t <= 13 ? 13 : t >= 28 ? 28 : t : null
}, l = function bgColor(t) {
return "#000000" !== t ? t : null
}, u = function color(t) {
return "#ffffff" !== t ? t : null
}, c = function opacity(t) {
return !isInteger(t) || t > 1 || t < .1 || .5 === t ? null : t
};
function queue(t) {
var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0;
return setTimeout((function () {
t()
}), e)
}
var p = [function breakpoint(t, e) {
if (t.getOption("breakpoint")) {
!function init() {
var n = t.getOption("columns"), o = t.getOption("breakpoint.columns"),
i = Number(e.getAttribute("data-columns")), r = function calculate(t, e) {
var n = window.innerWidth, o = Object.keys(t).filter((function (e) {
return n < t[e]
}));
return void 0 !== o[0] ? Number(o[0]) : e
}(o, n);
if (e.setAttribute("data-columns", n), void 0 !== r && i !== r) {
var a = t.getItems();
t.updateOption("columns", r), t.updateCurrentItems();
for (var s = 0; s < a.length; s += 1) t.go("columns", a[s]);
t.emit("breakpoint.changed")
}
}()
}
}, function transition(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = t.getOption("duration");
"stop" !== r ? (e.style.setProperty("transition-duration", "".concat(a, "ms")), t.on("destory", (function () {
e.style.removeProperty("transition-duration")
}))) : e.style.removeProperty("transition-duration")
}, function transform(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0];
e.style.setProperty("transform", "translateX(".concat(r, "px)")), t.on("destory", (function () {
e.style.removeProperty("transform")
}))
}, function autoplay(t) {
if (t.getOption("autoplay")) {
var e = t.getOption("autoplay.duration"), n = "left" === t.getOption("autoplay.direction") ? "<" : ">",
o = function repeat(t) {
return setInterval((function () {
t()
}), arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 5e3)
}((function () {
t.slideTo(n)
}), e);
t.on("destory", (function () {
!function stop(t) {
clearInterval(t), clearTimeout(t)
}(o)
}))
}
}, function grouping(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = o[1], s = t.getOption("grouping"), l = a + 1;
if (s) {
var u = t.getOption("columns"), c = Math.ceil((a + 1) / u);
r.setAttribute("data-order", c)
} else r.setAttribute("data-order", l);
t.on("destory", (function () {
r.removeAttribute("data-order")
}))
}, function columns(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = t.getOption("columns"), s = parseFloat((1 / a * 100).toFixed(2));
r.style.setProperty("flex", "0 0 ".concat(s, "%")), t.on("destory", (function () {
r.style.removeProperty("flex")
}))
}, function preview(t, e) {
if (t.getOption("preview")) {
var n = t.getOption("preview.edge");
e.style.setProperty("padding", "0 ".concat(n, "px")), t.on("destory", (function () {
e.style.removeProperty("padding")
}))
}
}, function spacing(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = Math.floor(t.getOption("spacing") / 2);
r.style.setProperty("padding", "0px ".concat(a, "px")), t.on("destory", (function () {
r.style.removeProperty("padding")
}))
}, function align(t, e) {
var n = t.getOption("align");
"center" === n ? e.style.setProperty("align-items", "center") : "bottom" === n && e.style.setProperty("align-items", "flex-end"), t.on("destory", (function () {
e.style.removeProperty("align-items")
}))
}, function touch(t, e) {
if (t.getOption("touch")) {
!function init() {
var n = t.getOption("touch.threshold"), o = t.getOption("touch.duration"), i = t.adaptEvent(e),
r = {x: 0, y: 0, time: 0};
i.on("touchstart", (function (t) {
t.preventDefault();
var e = t.changedTouches[0];
r.x = e.pageX, r.y = e.pageY, r.time = (new Date).getTime()
})), i.on("touchmove", (function (t) {
t.preventDefault()
})), i.on("touchend", (function (e) {
e.preventDefault();
var i = e.changedTouches[0], a = (new Date).getTime() - r.time, s = Math.abs(i.pageX - r.x);
if (!(a > o || s < n)) {
var l = i.pageX > r.x ? ">" : "<";
t.slideTo(l)
}
}))
}()
}
}, function clone(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), r = 2; r < n; r++) o[r - 2] = arguments[r];
var a = o[0], s = o[1], l = t.getOption("columns"), u = t.getOption("preview"), c = t.getOption("loop");
if (u || c) {
var p = t.getItemCount(), d = t.getItems(), f = l, h = a.cloneNode(!0), v = null, m = !1;
h.classList.add(i), s < f && (e.appendChild(h), m = !0), s >= p - f && (m ? ((v = a.cloneNode(!0)).classList.add(i), e.insertBefore(v, d[0])) : e.insertBefore(h, d[0])), t.on("destory", (function () {
h.remove(), v && v.remove()
}))
}
}, function slide(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = o[1], s = t.getOption("grouping"), l = t.getOption("preview"),
u = t.getOption("duration"), c = t.getOption("columns"), p = t.getOption("loop"),
d = t.getItems()[0].offsetWidth, f = t.getPage(), h = p || l, v = f.maximum(), m = f.calculate(r, !1),
g = m < 1 || m > v, y = 0;
!p && g || (y = s ? d * (0 - (h ? 0 : -1) - m) * c : d * (1 - (h ? c : 0) - m), t.emit("slide.start"), t.go("transition", a), t.go("transform", y), t.updatePosition(m), g ? queue((function () {
m = f.calculate(r, g), y = s ? d * (0 - m) * c : d * (1 - c - m), t.go("transition", "stop"), t.go("transform", y), t.updatePosition(m), t.emit("slide.end")
}), u + 10) : t.emit("slide.end"))
}, function loop(t, e) {
var n = t.getOption("loop"), o = t.getOption("grouping");
if (n && o) {
var i = t.getItems(), r = t.getOption("columns"), a = t.getItemCount(), s = i[i.length - 1],
l = r - a % r, u = [];
if (l !== r && 1 !== r) {
for (var c = 1; c <= l; c += 1) {
var p = s.cloneNode(!0);
p.classList.add("sliderm__slide--empty"), p.innerHTML = "", e.appendChild(p), u.push(p)
}
t.updateCurrentItems(), t.on("destory", (function () {
u.forEach((function (t) {
t.remove()
}))
}))
}
}
}, function init(e) {
var n = e.getOption("duration"), o = e.getRoot();
o.classList.add(t), o.classList.remove("".concat(t, "--initialized")), o.classList.add("".concat(t, "--initialize")), e.on("initialized", (function () {
queue((function () {
o.classList.remove("".concat(t, "--initialize")), o.classList.add("".concat(t, "--initialized"))
}), n + 50)
}))
}], d = [function pagination(t) {
var e, i, r, a = function click(e) {
if (o === e.target.className) {
var n = Array.prototype.indexOf.call(i.childNodes, e.target) + 1;
t.slideTo(n)
}
}, s = function mark() {
var e = t.getPosition(), o = findDom(t.getRoot(), ".".concat(n)).children;
Array.from(o).forEach((function (t, n) {
var o = n + 1;
t.removeAttribute("data-active"), o === e && t.setAttribute("data-active", !0)
}))
}, l = function destory() {
r.off("click", a), t.off("slide.end", s), i.remove()
}, u = function init() {
!function render() {
var a = setDom("div", n);
e = t.getPage().maximum();
for (var s = 0; s < e; s += 1) {
var l = setDom("div", o);
0 === s && l.setAttribute("data-active", !0), a.append(l)
}
i = a, r = t.adaptEvent(i), t.getRoot().append(i)
}(), function listen() {
r.on("click", a), t.on("slide.end", s)
}()
};
t.on("destory", l), t.on("breakpoint.changed", (function () {
l(), u()
})), u()
}, function spinner(t) {
!function init() {
var e = t.getOption("spinner.color"), n = setDom("div", "sliderm__spinner");
n.style.setProperty("color", e), t.getRoot().append(n), t.on("destory", (function () {
n.remove()
}))
}()
}, function arrow(t) {
!function init() {
for (var e = [s, r, a, u, l, c], n = setDom("div", "sliderm__button--previous"), o = setDom("div", "sliderm__button--next"), i = t.adaptEvent(n), p = t.adaptEvent(o), d = null, f = null, h = 0; h < e.length; h += 1) {
var v = e[h].name, m = e[h](t.getOption("arrow.".concat(v)));
null !== m && ("bold" === v ? (d = setDom("span", "sliderm__icon-left--".concat(m)), f = setDom("span", "sliderm__icon-right--".concat(m))) : "shape" === v ? (n.classList.add("sliderm__button--".concat(m)), o.classList.add("sliderm__button--".concat(m))) : ("bgColor" === v ? v = "background-color" : "size" === v && (v = "font-size", m = "".concat(m, "px")), n.style.setProperty(v, m), o.style.setProperty(v, m)))
}
n.append(d), o.append(f), t.getRoot().append(n), t.getRoot().append(o), i.on("click", (function () {
t.slideTo("<")
})), p.on("click", (function () {
t.slideTo(">")
})), t.on("destory", (function () {
n.remove(), o.remove()
}))
}()
}];
function _defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
var f = function () {
function EventDispatcher() {
!function _classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, EventDispatcher), this.events = {}
}
return function _createClass(t, e, n) {
return e && _defineProperties(t.prototype, e), n && _defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(EventDispatcher, [{
key: "on", value: function on(t, e) {
Object.prototype.hasOwnProperty.call(this.events, t) || (this.events[t] = []), this.events[t].push(e)
}
}, {
key: "off", value: function off(t, e) {
var n = this;
void 0 === e ? delete this.events[t] : this.events[t].forEach((function (o, i) {
o === e && n.events[t].splice(i, 1)
}))
}
}, {
key: "emit", value: function emit(t) {
for (var e = arguments.length, n = new Array(e > 1 ? e - 1 : 0), o = 1; o < e; o++) n[o - 1] = arguments[o];
void 0 !== this.events[t] && Array.isArray(this.events[t]) && this.events[t].forEach((function (t) {
t.apply(void 0, n)
}))
}
}, {
key: "destory", value: function destory() {
delete this.events
}
}]), EventDispatcher
}();
function event_adapter_defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
var h = function () {
function EventAdapter(t) {
!function event_adapter_classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, EventAdapter), this.target = t, this.events = {}
}
return function event_adapter_createClass(t, e, n) {
return e && event_adapter_defineProperties(t.prototype, e), n && event_adapter_defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(EventAdapter, [{
key: "on", value: function on(t, e) {
this.events[t] = e, this.target.addEventListener(t, this.events[t])
}
}, {
key: "off", value: function off(t) {
this.target.removeEventListener(t, this.events[t])
}
}, {
key: "emit", value: function emit(t) {
void 0 !== this.events[t] && this.target.dispatchEvent(new Event(t))
}
}, {
key: "destory", value: function destory() {
for (var t = Object.keys(this.events), e = 0; e < t.length; e += 1) this.off(t[e]);
delete this.events
}
}, {
key: "mock", value: function mock(t, e) {
void 0 !== this.events[t] && this.events[t](e)
}
}]), EventAdapter
}();
function error(t) {
console.error("[Sliderm] ".concat(t))
}
const v = {
arrow: !0,
pagination: !0,
spinner: !0,
grouping: !1,
loop: !0,
preview: !1,
breakpoint: !0,
touch: !0,
autoplay: !1,
columns: 4,
duration: 1e3,
spacing: 10,
align: "center",
extensions: [],
_arrow: {color: "#ffffff", bgColor: "#000000", opacity: .5, size: 16, shape: "circle", bold: 2},
_preview: {edge: 40},
_spinner: {color: "#1cbbb4"},
_breakpoint: {columns: {4: !1, 3: 960, 2: 768, 1: 420}},
_touch: {threshold: 10, duration: 300},
_autoplay: {direction: "right", duration: 5e3}
};
function page_defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
var m = function () {
function Page(t) {
!function page_classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, Page), this.sliderm = t
}
return function page_createClass(t, e, n) {
return e && page_defineProperties(t.prototype, e), n && page_defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(Page, [{
key: "calculate", value: function calculate() {
for (var t = arguments.length, e = new Array(t), n = 0; n < t; n++) e[n] = arguments[n];
var o = e[0], i = e[1], r = this.sliderm.getOption("columns"),
a = this.sliderm.getOption("grouping"), s = this.sliderm.getItemCount(),
l = this.sliderm.getGroupCount(), u = this.sliderm.getPosition(), c = a ? l : s, p = u, d = 0;
if (a) {
var f = Math.ceil(u * r / r);
p = f
}
return "number" == typeof o ? d = o : ">" === o ? (d = p + 1) > c && i && (d = 1) : "<" === o && (d = p - 1) <= 0 && i && (d = c), d
}
}, {
key: "maximum", value: function maximum() {
var t = this.sliderm.getOption("loop"), e = this.sliderm.getOption("preview"),
n = this.sliderm.getOption("grouping"), o = this.sliderm.getOption("columns"), i = t || e;
return n ? this.sliderm.getGroupCount() : i ? this.sliderm.getItemCount() : this.sliderm.getItemCount() - o + 1
}
}]), Page
}();
function _toConsumableArray(t) {
return function _arrayWithoutHoles(t) {
if (Array.isArray(t)) return _arrayLikeToArray(t)
}(t) || function _iterableToArray(t) {
if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"]) return Array.from(t)
}(t) || _unsupportedIterableToArray(t) || function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")
}()
}
function _slicedToArray(t, e) {
return function _arrayWithHoles(t) {
if (Array.isArray(t)) return t
}(t) || function _iterableToArrayLimit(t, e) {
var n = null == t ? null : "undefined" != typeof Symbol && t[Symbol.iterator] || t["@@iterator"];
if (null == n) return;
var o, i, r = [], a = !0, s = !1;
try {
for (n = n.call(t); !(a = (o = n.next()).done) && (r.push(o.value), !e || r.length !== e); a = !0) ;
} catch (t) {
s = !0, i = t
} finally {
try {
a || null == n.return || n.return()
} finally {
if (s) throw i
}
}
return r
}(t, e) || _unsupportedIterableToArray(t, e) || function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")
}()
}
function _unsupportedIterableToArray(t, e) {
if (t) {
if ("string" == typeof t) return _arrayLikeToArray(t, e);
var n = Object.prototype.toString.call(t).slice(8, -1);
return "Object" === n && t.constructor && (n = t.constructor.name), "Map" === n || "Set" === n ? Array.from(t) : "Arguments" === n || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) ? _arrayLikeToArray(t, e) : void 0
}
}
function _arrayLikeToArray(t, e) {
(null == e || e > t.length) && (e = t.length);
for (var n = 0, o = new Array(e); n < e; n++) o[n] = t[n];
return o
}
function sliderm_defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
function _classPrivateMethodInitSpec(t, e) {
!function _checkPrivateRedeclaration(t, e) {
if (e.has(t)) throw new TypeError("Cannot initialize the same private elements twice on an object")
}(t, e), e.add(t)
}
function _classPrivateMethodGet(t, e, n) {
if (!e.has(t)) throw new TypeError("attempted to get private field on non-instance");
return n
}
var g = new WeakSet, y = new WeakSet, _ = new WeakSet, b = new WeakSet, P = new WeakSet, w = function () {
function Sliderm(t, n) {
!function sliderm_classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, Sliderm), _classPrivateMethodInitSpec(this, P), _classPrivateMethodInitSpec(this, b), _classPrivateMethodInitSpec(this, _), _classPrivateMethodInitSpec(this, y), _classPrivateMethodInitSpec(this, g);
var o = getDom(t);
o ? (this.options = Object.assign(v, n), this.event = new f, this.page = new m(this), this.root = o, this.initialized = !1, this.domEvents = [], this.itemCount = 0, this.position = 1, this.modules = {}, this.slider = findDom(this.root, ".".concat(e)), this.items = [], _classPrivateMethodGet(this, g, _initialize2).call(this)) : error('The DOM "'.concat(t, '" is invalid.'))
}
return function sliderm_createClass(t, e, n) {
return e && sliderm_defineProperties(t.prototype, e), n && sliderm_defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(Sliderm, [{
key: "adaptEvent", value: function adaptEvent(t) {
var e = new h(t);
return this.domEvents.push(e), e
}
}, {
key: "getPage", value: function getPage() {
return this.page
}
}, {
key: "getRoot", value: function getRoot() {
return this.root
}
}, {
key: "getItemCount", value: function getItemCount() {
return this.itemCount
}
}, {
key: "getGroupCount", value: function getGroupCount() {
return this.groupCount
}
}, {
key: "getItems", value: function getItems() {
return this.items
}
}, {
key: "getPosition", value: function getPosition() {
return this.position
}
}, {
key: "updatePosition", value: function updatePosition(t) {
this.position = t
}
}, {
key: "updateCurrentItems", value: function updateCurrentItems() {
return !this.initialized && (_classPrivateMethodGet(this, _, _updateItems2).call(this), _classPrivateMethodGet(this, b, _updateGroupCount2).call(this), !0)
}
}, {
key: "getOption", value: function getOption(t) {
var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : null,
n = void 0 !== this.options[t] ? this.options[t] : e;
if (t.includes(".")) try {
var o = t.split("."), i = _slicedToArray(o, 2), r = i[0], a = i[1];
return this.options["_".concat(r)][a]
} catch (t) {
return e
}
return n
}
}, {
key: "updateOption", value: function updateOption(t, e) {
if (t.includes(".")) try {
var n = _slicedToArray(t.split("."), 2), o = n[0], i = n[1];
this.options["_".concat(o)][i] = e
} catch (t) {
} else this.options[t] = e
}
}, {
key: "slideTo", value: function slideTo(t) {
this.go("slide", t)
}
}, {
key: "go", value: function go(t) {
var e;
if (void 0 !== this.modules[t]) {
for (var n = arguments.length, o = new Array(n > 1 ? n - 1 : 0), i = 1; i < n; i++) o[i - 1] = arguments[i];
(e = this.modules)[t].apply(e, [this, this.slider].concat(o))
} else error("Invalid module name: ".concat(t))
}
}, {
key: "on", value: function on(t, e) {
this.event.on(t, e)
}
}, {
key: "off", value: function off(t, e) {
this.event.off(t, e)
}
}, {
key: "emit", value: function emit(t) {
for (var e, n = arguments.length, o = new Array(n > 1 ? n - 1 : 0), i = 1; i < n; i++) o[i - 1] = arguments[i];
var r = [this].concat(o);
(e = this.event).emit.apply(e, [t].concat(_toConsumableArray(r)))
}
}, {
key: "destory", value: function destory() {
this.event.emit("destory"), this.event.destory(), this.domEvents.forEach((function (t) {
t.destory()
}))
}
}]), Sliderm
}();
function _initialize2() {
var t = this;
this.emit("initialize"), _classPrivateMethodGet(this, _, _updateItems2).call(this), _classPrivateMethodGet(this, b, _updateGroupCount2).call(this), _classPrivateMethodGet(this, P, _beforeMountExtensions2).call(this), _classPrivateMethodGet(this, y, _mountExtensions2).call(this), this.go("init"), this.go("breakpoint"), this.go("loop"), this.go("align"), this.go("touch"), this.go("preview"), this.go("autoplay"), this.items.forEach((function (e, n) {
t.go("columns", e), t.go("spacing", e), t.go("grouping", e, n), t.go("clone", e, n)
})), this.slideTo(1), this.initialized = !0, this.emit("initialized")
}
function _mountExtensions2() {
for (var t = 0; t < p.length; t += 1) "function" == typeof p[t] && (this.modules[p[t].name] = p[t]);
for (var e = 0; e < d.length; e += 1) "function" == typeof d[e] && this.getOption(d[e].name) && d[e](this)
}
function _updateItems2() {
this.items = Array.from(findDom(this.root, ".".concat(e)).children), this.itemCount = this.items.length
}
function _updateGroupCount2() {
var t = this.getOption("columns");
this.groupCount = Math.ceil(this.itemCount / t)
}
function _beforeMountExtensions2() {
for (var t = 0; t < this.options.extensions.length; t += 1) {
var e = this.options.extensions[t].name;
if ("" !== e) {
var n = this.options.extensions[t];
void 0 === this.options[e] ? p.push(n) : d.push(n)
}
}
}
window.Sliderm = w
})()
})();

Binary file not shown.

@ -9,18 +9,7 @@ let config =
function isLoggedIn() { function isLoggedIn() {
"use strict"; "use strict";
return UserInfo.Email && UserInfo.Email.length > 0; return UserInfo.Email && 0 < UserInfo.Email.length;
}
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) { async function handleResponse(data, successMessage, failureMessage) {
@ -30,7 +19,7 @@ async function handleResponse(data, successMessage, failureMessage) {
const statusMessage = document.createElement("div"); const statusMessage = document.createElement("div");
statusMessage.classList.add("status-message"); statusMessage.classList.add("status-message");
if (data.Status === 'Success') { if ('Success' === data.Status) {
statusMessage.innerText = successMessage; statusMessage.innerText = successMessage;
statusMessage.classList.add("success"); statusMessage.classList.add("success");
} else { } else {
@ -56,7 +45,7 @@ async function showDashboardGreeting() {
async function doAction(url, requestData, successMessage, failureMessage, silent) { async function doAction(url, requestData, successMessage, failureMessage, silent) {
"use strict"; "use strict";
const params = new FormData(); const params = new URLSearchParams();
for (const key in requestData) { for (const key in requestData) {
params.append(key, requestData[key]); params.append(key, requestData[key]);
@ -80,25 +69,6 @@ async function doAction(url, requestData, successMessage, failureMessage, silent
return data; return data;
} }
async function doSlicks() {
const elements = document.getElementsByClassName('sliderm');
Array.prototype.forEach.call(elements, function (element) {
// Initialize the Slick carousel on each element
const sliderm = new Sliderm('#' + element.id, {
arrow: true,
pagination: true,
grouping: false,
loop: true,
preview: false,
columns: 1,
duration: 1000,
spacing: 10,
align: 'center',
});
});
}
async function handlePageResponse(data) { async function handlePageResponse(data) {
"use strict"; "use strict";
const navbar = document.getElementById("navbar_container"); const navbar = document.getElementById("navbar_container");
@ -110,6 +80,7 @@ async function handlePageResponse(data) {
if (data.PageTitle) { if (data.PageTitle) {
document.title = data.PageTitle; document.title = data.PageTitle;
} }
if (data.Page) { if (data.Page) {
pageArea.innerHTML = data.Page; pageArea.innerHTML = data.Page;
if (data.PageLocation) { if (data.PageLocation) {
@ -133,11 +104,9 @@ async function displayList(data, elementId, deleteFunction) {
headerRow.appendChild(th); headerRow.appendChild(th);
} }
if (typeof deleteFunction === "function") { if ("function" === typeof deleteFunction) {
const th = document.createElement("th"); const th = document.createElement("th");
let deleteBtn = document.createElement('i'); th.appendChild(document.createTextNode("Delete"));
deleteBtn.classList.add("ri-delete-bin-line");
th.appendChild(deleteBtn);
headerRow.appendChild(th); headerRow.appendChild(th);
} }
@ -148,10 +117,10 @@ async function displayList(data, elementId, deleteFunction) {
td.appendChild(document.createTextNode(line[key])); td.appendChild(document.createTextNode(line[key]));
dataRow.appendChild(td); dataRow.appendChild(td);
} }
if (typeof deleteFunction === "function") { if ("function" === typeof deleteFunction) {
const td = document.createElement("td"); const td = document.createElement("td");
const deleteButton = document.createElement('button'); const deleteButton = document.createElement('button');
deleteButton.innerHTML = "<i class='ri-delete-bin-line'></i>"; deleteButton.textContent = "Delete";
deleteButton.onclick = () => deleteFunction(line.ID); deleteButton.onclick = () => deleteFunction(line.ID);
td.appendChild(deleteButton); td.appendChild(deleteButton);
dataRow.appendChild(td); dataRow.appendChild(td);
@ -220,14 +189,6 @@ async function togglearticlecreate() {
articleContainerElement.classList.toggle("hidden"); articleContainerElement.classList.toggle("hidden");
} }
async function togglememecreate() {
"use strict";
let memeContainerElement = document.getElementById("memecreatecontainer");
await getMemeImages();
memeContainerElement.classList.toggle("hidden");
}
async function renderarticles() { async function renderarticles() {
"use strict"; "use strict";
let template = document.querySelector('template[data-template-name="article"]').innerHTML; let template = document.querySelector('template[data-template-name="article"]').innerHTML;
@ -271,13 +232,13 @@ async function articleInit() {
let articleContainerElement = document.getElementById("articlecreatecontainer"); let articleContainerElement = document.getElementById("articlecreatecontainer");
let articleCreateOpenElement = document.getElementById("articlecreateopen"); let articleCreateOpenElement = document.getElementById("articlecreateopen");
articleContainerElement.addEventListener("keyup", function (ev) { articleContainerElement.addEventListener("keyup", function (ev) {
if (ev.key === "Escape") { if ("Escape" === ev.key) {
togglearticlecreate(); togglearticlecreate();
} }
}); });
PageIntervals.push(setInterval(renderarticles, config.articleRefresh)); PageIntervals.push(setInterval(renderarticles, config.articleRefresh));
document.getElementById("articleprivilegeinput").setAttribute("max", UserInfo.Privileges); document.getElementById("articleprivilegeinput").setAttribute("max", UserInfo.Privileges);
if (UserInfo.Privileges < 2) { if (2 > UserInfo.Privileges) {
articleContainerElement.style.display = "none"; articleContainerElement.style.display = "none";
articleCreateOpenElement.style.display = "none"; articleCreateOpenElement.style.display = "none";
} else { } else {
@ -295,7 +256,7 @@ async function onPageLoad() {
clearInterval(interval); clearInterval(interval);
} }
if (currentSite === "account" && currentPage === "settings") { if ("home" === currentSite && "settings" === currentPage) {
if (document.getElementById("user-settings")) { if (document.getElementById("user-settings")) {
await populateUserInfoFields(UserInfo); await populateUserInfoFields(UserInfo);
} }
@ -304,19 +265,12 @@ async function onPageLoad() {
await listUsers(true); await listUsers(true);
} }
} }
if (currentSite === "account" && currentPage === "index" && isLoggedIn()) { if ("account" === currentSite && "index" === currentPage && isLoggedIn()) {
await showDashboardGreeting(); await showDashboardGreeting();
} }
if (currentSite === "news" && currentPage === "index") { if ("news" === currentSite && "index" === currentPage) {
await articleInit(); await articleInit();
} }
if (currentSite === "account" && currentPage === "files") {
await listFiles();
}
if (currentSite === "memes" && currentPage === "index") {
await getMemeImages();
}
await doSlicks();
} }
async function navigateTo(site, page) { async function navigateTo(site, page) {
@ -362,7 +316,6 @@ async function logout() {
await refreshNavbar(); await refreshNavbar();
await navigateTo(localStorage.getItem("defaultSite"), localStorage.getItem("defaultPage")); await navigateTo(localStorage.getItem("defaultSite"), localStorage.getItem("defaultPage"));
localStorage.clear(); localStorage.clear();
UserInfo = {};
umami.track("logout"); umami.track("logout");
}) })
.catch((error) => { .catch((error) => {
@ -500,7 +453,7 @@ async function getUserInfo() {
}; };
const result = await doAction('/account', data, "User info retrieved Successfully!", "User info retrieval failed.", true); const result = await doAction('/account', data, "User info retrieved Successfully!", "User info retrieval failed.", true);
if (result && result.Status === "Success") { if (result && "Success" === result.Status) {
Object.keys(result.UserInfo).forEach(index => { Object.keys(result.UserInfo).forEach(index => {
let value = result.UserInfo[index]; let value = result.UserInfo[index];
localStorage.setItem("UserInfo_" + index, value); localStorage.setItem("UserInfo_" + index, value);
@ -537,7 +490,7 @@ async function listUsers(silent) {
doAction('/account', data, "User list retrieved Successfully!", "User list retrieval failed.", silent).then((result) => { doAction('/account', data, "User list retrieved Successfully!", "User list retrieval failed.", silent).then((result) => {
if (result && result.Status === "Success") { if (result && "Success" === result.Status) {
displayList(result.Users, "userListTable", deleteUser); displayList(result.Users, "userListTable", deleteUser);
} }
}); });
@ -580,191 +533,8 @@ async function deleteActivationCode(activationCode) {
//Admin settings end //Admin settings end
if (document.readyState === "loading") { if ("loading" === document.readyState) {
document.addEventListener("DOMContentLoaded", initAjax); document.addEventListener("DOMContentLoaded", initAjax);
} else { } else {
setTimeout(initAjax, 0); 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');
}

@ -80,10 +80,6 @@ li {
list-style: none; list-style: none;
} }
nav li {
margin: unset;
}
header ul li { header ul li {
list-style: circle; list-style: circle;
width: fit-content; width: fit-content;
@ -137,8 +133,6 @@ table>tbody>tr>td>button {
border-collapse: unset; border-collapse: unset;
padding: 0; padding: 0;
margin: 0; margin: 0;
width: 100%;
height: 100%;
} }
table { table {
@ -151,10 +145,9 @@ ul.navpage_list {
display: none; display: none;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
position: fixed;
transition: max-height .3s ease, border .325s ease !important; transition: max-height .3s ease, border .325s ease !important;
z-index: 2; z-index: 2;
position: fixed;
background: #00000066;
} }
.back { .back {
@ -183,7 +176,7 @@ ul.navpage_list {
.navsite_item:hover .navpage_list { .navsite_item:hover .navpage_list {
border: 4px solid var(--pico-primary-hover) !important; border: 4px solid var(--pico-primary-hover) !important;
display: flex !important; display: flex !important;
max-height: unset; max-height: 200px;
width: inherit; width: inherit;
transition: max-height .3s ease, border .325s ease !important; transition: max-height .3s ease, border .325s ease !important;
box-sizing: border-box; box-sizing: border-box;
@ -214,9 +207,67 @@ ul.navpage_list {
height: 1.5rem; height: 1.5rem;
} }
@media (max-width: 1050px) {
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;
}
#navsite_list li {
text-align: center;
}
.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: border-box;
transition-delay: .1s;
}
.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(--dimmer);
box-shadow: 0 20px 28px 0 var(--dimmer);
top: 80px;
text-align: center;
}
nav {
flex-direction: column;
align-items: center;
background-color: var(--dimmer);
}
}
#statusMessageContainer { #statusMessageContainer {
position: fixed; position: fixed;
top: 100px; top: 80px;
right: 20px; right: 20px;
z-index: 510; z-index: 510;
display: flex; display: flex;
@ -294,7 +345,8 @@ span#ye-span:hover + body{
} }
#articlecreate, #memecreate { #articlecreate {
position: fixed;
border: 5px solid var(--pico-primary); border: 5px solid var(--pico-primary);
z-index: 5; z-index: 5;
margin: auto; margin: auto;
@ -311,15 +363,15 @@ span#ye-span:hover + body{
display: none; display: none;
} }
#articlecreatecontainer, #memecreatecontainer{ #articlecreatecontainer{
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: fixed; position: fixed;
top: 12vh; top: 0;
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 88vh; height: 100vh;
z-index: 4; z-index: 4;
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
} }
@ -343,162 +395,7 @@ div#articleslist>article{
border: 4px solid var(--pico-primary); border: 4px solid var(--pico-primary);
} }
.form-content { #dynmapa {
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%; width: 100%;
justify-content: right; height: 100%;
}
.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%;
}
} }

@ -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"],
};
}

@ -11,12 +11,6 @@ function endpoint($endpoint_data): array
$endpoint_data["title"], $endpoint_data["title"],
$endpoint_data["body"] $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"], 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"],
};
}

@ -8,11 +8,7 @@ function endpoint($endpoint_data): array
return match ($endpoint_data["action"]) { return match ($endpoint_data["action"]) {
"getMyFiles" => listFiles(), "getMyFiles" => listFiles(),
"getAllFiles" => listFiles(false), "getAllFiles" => listFiles(false),
"uploadFiles" => parseIncomingFiles(), "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"], default => ["Status" => "Fail", "message" => "Invalid action"],
}; };
} }

@ -1,40 +1,32 @@
<?php <?php
/** @noinspection PhpIncludeInspection */ /** @noinspection PhpIncludeInspection */
// Include essential configuration and function libraries. require_once 'secrets/config.php';
require_once 'secrets/config.php'; // Load sensitive configuration such as database credentials. require_once 'lib/config.php';
require_once 'lib/config.php'; // Load general site configuration settings. require_once 'lib/navigation.php';
require_once 'lib/navigation.php'; // Include functions related to navigation generation. require_once 'lib/router.php';
require_once 'lib/router.php'; // Include routing functionality to manage URL routing. require_once 'lib/page.php';
require_once 'lib/page.php'; // Functions related to page content generation and management. require_once 'lib/endpoint.php';
require_once 'lib/endpoint.php'; // Functions for handling API endpoints. require_once 'lib/account.php';
require_once 'lib/account.php'; // Include user account management functionality.
// Load configuration for the router from the configuration files.
$routerConfig = loadRouterConfig(); $routerConfig = loadRouterConfig();
// Initialize the router to parse the request URI and determine the requested site/page.
$routerRequest = initRouter(); $routerRequest = initRouter();
// Start or resume a session to manage user sessions across requests.
session_start(); session_start();
// Set default session data if the user is not logged in.
if (!isLoggedIn()) { if (!isLoggedIn()) {
setDefaultSessionData(); setDefaultSessionData();
} }
// Handle requests for the sitemap. if($routerRequest["site_name"] == "sitemap.xml") {
if ($routerRequest["site_name"] == "sitemap.xml") { require "lib/sitemap.php";
require "lib/sitemap.php"; // Include sitemap generation functions. generateSitemap();
echo generateSitemap(); // Generate and output the sitemap XML. exit();
exit(); // Stop script execution after sitemap generation.
} }
// Handle API type requests by fetching and outputting the endpoint response.
if ($routerRequest["type"] == "api") { if ($routerRequest["type"] == "api") {
echo getEndpoint($routerRequest["site_name"]); echo getEndpoint($routerRequest["site_name"]);
}
// Handle page type requests by fetching and rendering the page content. } elseif ($routerRequest["type"] == "page") {
elseif ($routerRequest["type"] == "page") { echo getPage($routerRequest["site_name"], $routerRequest["page_name"]);
echo getPage($routerRequest["site_name"], $routerRequest["page_name"]);
} }

@ -1,95 +1,52 @@
<?php <?php
use Random\RandomException; 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 function isLoggedIn(): bool
{ {
global $routerConfig; 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["permissions"]["logged_in_default"];
} }
/**
* 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 function isVerified(): bool
{ {
global $routerConfig; global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["verified"]; return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["verified"];
} }
/**
* 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 function isTrustWorthy(): bool
{ {
global $routerConfig; global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["trustworthy"]; return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["trustworthy"];
} }
/**
* 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 function isModerator(): bool
{ {
global $routerConfig; global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["moderator"]; return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["moderator"];
} }
/**
* 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 function isUserAdmin(): bool
{ {
global $routerConfig; global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["user_admin"]; return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["user_admin"];
} }
/**
* 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 function isAdmin(): bool
{ {
global $routerConfig; global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["admin"]; return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["admin"];
} }
/**
* Generates a secure token for account activation or other purposes using cryptographic methods. function generateActivationToken(): string
*
* @return string|null Returns a hexadecimal token or null in case of an error.
*/
function generateActivationToken(): ?string
{ {
try { try {
return bin2hex(random_bytes(16)); return bin2hex(random_bytes(16));
} catch (RandomException) { } catch (RandomException) {
return null;
} }
} }
/**
* Checks if an email address is available for registration. function isEmailAvailable($email): bool
*
* @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
{ {
global $mysqli; global $mysqli;
$stmt = $mysqli->prepare("SELECT COUNT(*) FROM Users WHERE Email = ?"); $stmt = $mysqli->prepare("SELECT COUNT(*) FROM Users WHERE Email = ?");
@ -102,12 +59,7 @@ function isEmailAvailable(string $email): bool
return $count === 0; 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 function setDefaultSessionData(): void
{ {
global $routerConfig; global $routerConfig;
@ -119,15 +71,8 @@ function setDefaultSessionData(): void
$_SESSION["minecraft_nickname"] = ""; $_SESSION["minecraft_nickname"] = "";
$_SESSION["privilege_level"] = $routerConfig["permissions"]["logged_out"]; $_SESSION["privilege_level"] = $routerConfig["permissions"]["logged_out"];
} }
/**
* Verifies if the provided password matches the stored hash for the user. function verifyPassword($userID, $password): bool
*
* @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
{ {
global $mysqli; global $mysqli;
$stmt = $mysqli->prepare("SELECT PasswordHash FROM Users WHERE ID = ?"); $stmt = $mysqli->prepare("SELECT PasswordHash FROM Users WHERE ID = ?");
@ -140,12 +85,7 @@ function verifyPassword(int $userID, string $password): bool
return !empty($password_hash) && !empty($password) && password_verify($password, $password_hash); 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(): void
{ {
global $mysqli; global $mysqli;
@ -178,15 +118,8 @@ function UpdateSession(): void
$_SESSION["favorite_color"] = $favorite_color; $_SESSION["favorite_color"] = $favorite_color;
} }
/**
* Attempts to log in a user with the given credentials. function doLogin($email, $password): array
*
* @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
{ {
global $mysqli; global $mysqli;
$found = false; $found = false;
@ -216,12 +149,7 @@ function doLogin(string $email, string $password): array
} }
return $found ? ["Status" => "Success"] : ["Status" => "Fail"]; 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 function doLogout(): array
{ {
if(isLoggedIn()){ if(isLoggedIn()){
@ -231,19 +159,8 @@ function doLogout(): array
return ["Status" => "Fail"]; return ["Status" => "Fail"];
} }
} }
/**
* Registers a new user with provided personal details and activation token. function doRegister($firstname, $lastname, $email, $password, $activation_token): array
*
* @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
{ {
global $mysqli, $routerConfig; global $mysqli, $routerConfig;
$status = ["Status" => "Fail"]; $status = ["Status" => "Fail"];
@ -269,15 +186,8 @@ function doRegister(string $firstname, string $lastname, string $email, string $
return $status; return $status;
} }
/**
* Changes the user's password after verifying the old password. function changePassword($oldPassword, $newPassword): array
*
* @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
{ {
global $mysqli; global $mysqli;
$status = ["Status" => "Fail"]; $status = ["Status" => "Fail"];
@ -296,34 +206,23 @@ function changePassword(string $oldPassword, string $newPassword): array
} }
/** // Function to update user profile
* Updates user profile information in the database. function updateUserProfile($firstName, $lastName, $nickname, $minecraft_nickname): array
*
* @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
{ {
global $mysqli; global $mysqli;
$status = ["Status" => "Fail"]; $status = ["Status" => "Fail"];
if (isLoggedIn() && !empty($firstName) && !empty($lastName) && !empty($nickname) && !empty($minecraft_nickname)) { 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 = ?"); $stmt = $mysqli->prepare("UPDATE Users SET FirstName = ?, LastName = ?, Nickname = ?, MinecraftNick = ? WHERE ID = ?");
/** @noinspection SpellCheckingInspection */ /** @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(); $stmt->execute();
if ($stmt->affected_rows > 0) { if ($stmt->affected_rows > 0) {
$status["Status"] = "Success"; $status["Status"] = "Success";
} }
else {
$status["Status"] = "$firstName $lastName $nickname $minecraft_nickname";
}
$stmt->close(); $stmt->close();
} }
@ -331,14 +230,8 @@ function updateUserProfile(string $firstName, string $lastName, string $nickname
return $status; return $status;
} }
/** // Function to update user email
* Updates the email address of the logged-in user after validation. function updateUserEmail($email): array
*
* @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
{ {
global $mysqli; global $mysqli;
$status = ["Status" => "Fail"]; $status = ["Status" => "Fail"];
@ -381,11 +274,7 @@ function updateUserEmail(string $email): array
return $status; 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 function getUserInfo(): array
{ {
$output = ["Status" => "Fail"]; $output = ["Status" => "Fail"];
@ -421,14 +310,8 @@ function getUserInfo(): array
return $output; return $output;
} }
/**
* Generates a specified number of activation codes for user registration and adds them to the database. function addActivationCodes($count): array
*
* @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
{ {
global $mysqli; global $mysqli;
$activationCodes = []; $activationCodes = [];
@ -459,12 +342,7 @@ function addActivationCodes(int $count): array
return $output; 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 function listUsers(): array
{ {
global $mysqli; global $mysqli;
@ -486,12 +364,7 @@ function listUsers(): array
return $output; 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 function listActivationCodes(): array
{ {
global $mysqli; global $mysqli;
@ -536,14 +409,8 @@ function listActivationCodes(): array
return $output; return $output;
} }
/**
* Deletes a user by their ID, available only to user admins. function deleteUser($userID): array
*
* @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
{ {
global $mysqli; global $mysqli;
$status = ["Status" => "Fail"]; $status = ["Status" => "Fail"];
@ -558,14 +425,8 @@ function deleteUser(int $userID): array
} }
return $status; return $status;
} }
/**
* Deletes an activation code, available only to user admins. function deleteActivationCode($activationCode): array
*
* @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
{ {
global $mysqli; global $mysqli;
$status = ["Status" => "Fail"]; $status = ["Status" => "Fail"];

@ -1,29 +1,5 @@
<?php <?php
/** function loadRouterConfig(): array
* 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
{ {
return [ return [
@ -54,9 +30,6 @@ function loadRouterConfig(): array
'newsarticle' => [ 'newsarticle' => [
'default_permissions' => 255, 'default_permissions' => 255,
], ],
'meme' => [
'per_page' => 10
],
'seo' => [ 'seo' => [
'author' => 'Tím AdlerkaTop', 'author' => 'Tím AdlerkaTop',
'description' => 'Toto je neoficiánla študentská stránka pre Adlerku, kde môžete nájsť plno zaujímavostí.', 'description' => 'Toto je neoficiánla študentská stránka pre Adlerku, kde môžete nájsť plno zaujímavostí.',

@ -1,11 +1,5 @@
<?php <?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 function doDynamicStyling() :string
{ {
$dynamic_style = ""; $dynamic_style = "";

@ -1,16 +1,6 @@
<?php <?php
/**
* Executes an endpoint script and returns the results. function runEndpoint($endpoint_file): ?array
*
* 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
{ {
$endpoint_data = $_POST; $endpoint_data = $_POST;
@ -19,20 +9,8 @@ function runEndpoint(string $endpoint_file): ?array
return endpoint($endpoint_data); return endpoint($endpoint_data);
} }
/**
* Retrieves and processes the output of a specified endpoint. function getEndpoint($endpoint_name): string
*
* 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
{ {
$output = array(); $output = array();
$output["Status"] = "Fail"; $output["Status"] = "Fail";
@ -46,22 +24,11 @@ function getEndpoint(string $endpoint_name): string
$endpoint_file = $routerConfig["endpoint_dir"] . $endpoint_name . ".php"; $endpoint_file = $routerConfig["endpoint_dir"] . $endpoint_name . ".php";
if (file_exists($endpoint_file)){ if (file_exists($endpoint_file)){
$output_tmp = runEndpoint($endpoint_file); $output = 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);
}
} }
else{ else{
$output["Error"] = "Not found"; $output["Error"] = "Not found";
$output["Endpoint"] = $endpoint_name;
http_response_code(404); http_response_code(404);
} }

@ -1,13 +1,6 @@
<?php <?php
/**
* Processes an HTML string to inline all linked stylesheets by replacing <link> tags function inlineLocalStylesFromHref($inputString): string
* 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
{ {
$pattern = '/<link[^>]*?\srel=["\']?stylesheet["\'].*?\shref=["\']?\/(.*?)["\'][^>]*?>/i'; $pattern = '/<link[^>]*?\srel=["\']?stylesheet["\'].*?\shref=["\']?\/(.*?)["\'][^>]*?>/i';
@ -37,15 +30,8 @@ function inlineLocalStylesFromHref(string $inputString): string
return "<style>$cssContent</style>"; return "<style>$cssContent</style>";
}, $inputString); }, $inputString);
} }
/**
* Processes an HTML string to inline all external JavaScript files by replacing <script src="..."> tags function inlineScriptFromSrc($inputString): string
* 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
{ {
$pattern = '/<script.*?src=["\']\/(.*?)["\'].*?>\s*<\/script>/i'; $pattern = '/<script.*?src=["\']\/(.*?)["\'].*?>\s*<\/script>/i';
@ -58,14 +44,8 @@ function inlineScriptFromSrc(string $inputString): string
return "<script>$jsContent</script>"; return "<script>$jsContent</script>";
}, $inputString); }, $inputString);
} }
/**
* Minifies CSS content by removing comments, unnecessary whitespaces, semicolons, and optimizing other aspects of the stylesheet. function minifyCss($css): string
* 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
{ {
// Remove comments // Remove comments
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css); $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
@ -84,14 +64,8 @@ function minifyCss(string $css): string
return trim($css); return trim($css);
} }
/**
* Minifies JavaScript content by removing comments, unnecessary whitespaces, and optimizing spaces around operators. function minifyJs($js): string
* 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
{ {
// Remove newlines and tabs // Remove newlines and tabs

@ -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,24 +1,11 @@
<?php <?php
/**
* Includes a PHP file that returns metadata associated with a dynamic page. function getDynamicMetadata($file): array{
* 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); return include($file);
} }
/**
* Extracts and validates the minimal permission level required to access certain content, function getDynamicPermission($metadata): int {
* 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 {
global $routerConfig; global $routerConfig;
$params = $metadata["parameters"]; $params = $metadata["parameters"];
try { try {
@ -34,14 +21,7 @@ function getDynamicPermission(array $metadata): int {
return $permission_level; 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 function generateNavigation(): string
{ {
global $routerConfig; global $routerConfig;
@ -127,12 +107,7 @@ function generateNavigation(): string
return str_replace("__NAV_PAGES__", $nav_out, $nav); return str_replace("__NAV_PAGES__", $nav_out, $nav);
} }
/**
* 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{ function getNavigationEndpoint() :array{
return [ return [
"Status" => "Success", "Status" => "Success",

@ -1,15 +1,4 @@
<?php <?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 function getNewsArticles() :array
{ {
global $mysqli; global $mysqli;
@ -50,18 +39,7 @@ function getNewsArticles() :array
return $output; return $output;
} }
/** function addNewsArticle($title="Nazov", $body="Obsah", $privilegeLevel=0) :array
* 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 $mysqli;
global $routerConfig; global $routerConfig;
@ -84,47 +62,3 @@ function addNewsArticle(string $title="Nazov", string $body="Obsah", int $privil
} }
return $output; 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;
}

@ -1,37 +1,16 @@
<?php <?php
require_once "lib/dynamic_style.php"; require_once "lib/dynamic_style.php";
require_once "lib/script_data.php"; require_once "lib/script_data.php";
/** function renderDynamicPage($page_file): array
* 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
{ {
return require $page_file; return require $page_file;
} }
/** function removeHtmlComments($content = '') :string {
* 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); return preg_replace('/<!--(.|\s)*?-->/', '', $content);
} }
/**
* Parses custom `<page>` tags from the given input string and extracts parameters. function parsePageTag($input): array
* 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
{ {
// Define the pattern for the tag // Define the pattern for the tag
$pattern = '/<page\s+([^>]+)><\/page>/i'; $pattern = '/<page\s+([^>]+)><\/page>/i';
@ -55,15 +34,8 @@ function parsePageTag(string $input): array
// If no match is found, return the original input // If no match is found, return the original input
return ['parameters' => [], 'output' => $input]; return ['parameters' => [], 'output' => $input];
} }
/**
* Renders a page based on specified page and site names, handling dynamic and static content, function renderPage($page_name = null, $site_name = null): array
* 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
{ {
global $routerConfig; global $routerConfig;
global $routerRequest; global $routerRequest;
@ -166,16 +138,8 @@ function renderPage(string $page_name = null, string $site_name = null): array
]; ];
} }
/**
* Compiles a complete web page by injecting dynamic elements into a template skeleton, function getPage($site_name_in = null, $page_name_in = null): string
* 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_tmp = renderPage($page_name_in, $site_name_in);
@ -259,15 +223,8 @@ function getPage(string $site_name_in = null, string $page_name_in = null): stri
} }
return str_replace("__TEMPLATE_PAGE_TITLE__", $page_title, $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. function getPageEndpoint($page_name, $site_name) :array
* 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_location = "/" . $site_name . "/" . $page_name;
$page_tmp = renderPage($page_name, $site_name); $page_tmp = renderPage($page_name, $site_name);

@ -1,14 +1,6 @@
<?php <?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(): array
{ {
global $routerConfig; global $routerConfig;

@ -1,22 +1,12 @@
<?php <?php
/** function generateScriptData($phpArray):string {
* 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 // 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)) { 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 // Generate JavaScript code to save each array element to local storage
$out = "<script>"; $out = "<script>";
foreach ($phpArray as $key => $value) { foreach ($phpArray as $key => $value) {
$escapedKey = addslashes(strval($key)); // Escape special characters in the key $escapedKey = addslashes($key); // Escape special characters in the key
$escapedValue = addslashes(strval($value)); // Escape special characters in the value $escapedValue = addslashes($value); // Escape special characters in the value
$out .= "localStorage.setItem('$escapedKey', '$escapedValue');"; $out .= "localStorage.setItem('$escapedKey', '$escapedValue');";
} }

@ -1,16 +1,8 @@
<?php <?php
require_once "lib/account.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 function generateSitemap(): void{
* 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; global $routerConfig;
$site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..')); $site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..'));
@ -62,5 +54,5 @@ function generateSitemap(): string{
$sitemap .= '</urlset>' . PHP_EOL; $sitemap .= '</urlset>' . PHP_EOL;
header('Content-type: application/xml'); header('Content-type: application/xml');
return $sitemap; echo $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;
}

@ -1,12 +1,6 @@
<?php <?php
/**
* Sanitizes user input to be used as a part of a file path to prevent security vulnerabilities such as directory traversal. function makePathSafe($userInput): string
*
* @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 // Keep only alphanumeric characters, underscores, and hyphens
$safeString = preg_replace('/[^\w\-]/', '', $userInput); $safeString = preg_replace('/[^\w\-]/', '', $userInput);
@ -23,41 +17,7 @@ function makePathSafe(string $userInput): string
// Limit length for safety // Limit length for safety
return substr($safeString, 0, 255); 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 function getIncomingFiles(): array
{ {
$files = $_FILES; $files = $_FILES;
@ -82,82 +42,69 @@ function getIncomingFiles(): array
} }
return $files3; return $files3;
} }
/**
* Saves file metadata in the database. function saveUploadedFileInDatabase($filePath, $fileType):bool
* 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; global $mysqli;
$stmt = $mysqli->prepare("INSERT INTO Files (Path, Type, UploadedBy, UploadedAt, Width, Height) VALUES (?, ?, ?, NOW(), ?, ?)"); $stmt = $mysqli->prepare("INSERT INTO Files (Path, Type, UploadedBy, UploadedAt) VALUES (?, ?, ?, NOW())");
$stmt->bind_param("ssiii", $filePath, $fileType, $_SESSION["ID"], $width, $height); $stmt->bind_param("ssi", $filePath, $fileType, $_SESSION["ID"]);
$stmt->execute(); $stmt->execute();
$stat = $stmt->affected_rows > 0; $stat = $stmt->affected_rows > 0;
$stmt->close(); $stmt->close();
return $stat; return $stat;
} }
/**
* Handles the uploading process of an image, including its conversion to webp format, rotation based on orientation, and saving. function doImageUpload($inFile, $outFile): bool
*
* @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 // Create Imagick object
$width = 0;
$height = 0;
try { try {
$imagick = new Imagick($inFile); $imagick = new Imagick($inFile);
$imagick->setImageFormat('webp'); } catch (ImagickException $e) {
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. // Set the desired format for reencoding (WebP)
try {
$imagick->setImageFormat('webp');
} catch (ImagickException $e) {
}
// Remove non-essential metadata
try {
$imagick->stripImage();
} catch (ImagickException $e) {
}
// Write the reencoded image to the output file
try {
$imagick->writeImage($outFile);
} catch (ImagickException $e) {
}
// Destroy the Imagick object to free up resources
$imagick->destroy();
// Check if the reencoding was successful
if (file_exists($outFile)) { if (file_exists($outFile)) {
return saveUploadedFileInDatabase($outFile, 'image/webp', $width, $height); return saveUploadedFileInDatabase($outFile, 'image/webp');
} else { } else {
return false; 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 function listFiles($onlyMine = true):array
{ {
$output = ["Status" => "Success", "Files" => []]; $output = ["Status" => "Fail"];
require_once "lib/account.php"; require_once "lib/account.php";
if (isLoggedIn()) { if(($onlyMine && isLoggedIn()) || (!$onlyMine && isModerator())) {
global $mysqli; global $mysqli;
if (!$onlyMine && !isModerator()) { $query = "SELECT ID, Path, Type, UploadedAt, UploadedBy FROM Files";
$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) { if($onlyMine){
$query .= " WHERE UploadedBy = ?"; $query .= " WHERE UploadedBy = ?";
} }
$stmt = $mysqli->prepare($query); $stmt = $mysqli->prepare($query);
if ($onlyMine) { if($onlyMine) {
$stmt->bind_param("i", $_SESSION["ID"]); $stmt->bind_param("i", $_SESSION["ID"]);
} }
@ -165,13 +112,12 @@ function listFiles(bool $onlyMine = true): array
$path = ""; $path = "";
$type = ""; $type = "";
$uploadedAt = ""; $uploadedAt = "";
$uploadedByID = 0; $uploadedBy = 0;
$uploadedBy = "";
$stmt->bind_result($id, $path, $type, $uploadedAt, $uploadedByID, $uploadedBy); $stmt->bind_result($id, $path, $type, $uploadedAt, $uploadedBy);
$stmt->execute(); $stmt->execute();
$files = array();
// Fetch the results into the bound variables // Fetch the results into the bound variables
while ($stmt->fetch()) { while ($stmt->fetch()) {
$files[] = [ $files[] = [
@ -179,13 +125,15 @@ function listFiles(bool $onlyMine = true): array
'Path' => $path, 'Path' => $path,
'Type' => $type, 'Type' => $type,
'UploadedAt' => $uploadedAt, 'UploadedAt' => $uploadedAt,
'UploadedByID' => $uploadedByID,
'UploadedBy' => $uploadedBy, 'UploadedBy' => $uploadedBy,
]; ];
} }
// Check if any results were fetched // Check if any results were fetched
if (!empty($files)) {
$output["Status"] = "Success";
$output["Files"] = $files; $output["Files"] = $files;
}
$stmt->close(); $stmt->close();
} }
@ -193,21 +141,16 @@ function listFiles(bool $onlyMine = true): array
return $output; 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 function parseIncomingFiles(): array
{ {
$incomingFiles = getIncomingFiles(); $incomingFiles = getIncomingFiles();
$success = true; $success = true;
foreach ($incomingFiles as $incomingFile) { foreach ($incomingFiles as $incomingFile) {
if ($incomingFile["error"] == 0 && is_file($incomingFile["tmp_name"])) { if ($incomingFile["error"] == 0 && is_file($incomingFile["tmp_name"])) {
$type = explode("/", $incomingFile["type"]); $type = explode("/", $incomingFile["type"]);
if ($type[0] == "image") { if ($type == "image") {
$imgFname = pathinfo($incomingFile["name"], PATHINFO_FILENAME); $imgFname = pathinfo($incomingFile["name"], PATHINFO_FILENAME);
$uploadPath = getUploadPath("image", $imgFname); $uploadPath = getUploadPath("image", $imgFname);
if (!empty($uploadPath)) { if (!empty($uploadPath)) {
@ -221,130 +164,26 @@ function parseIncomingFiles(): array
} }
} }
$output = ["Status" => "Fail"]; $output = ["Status" => "Fail"];
if ($success) { if($success){
$output["Status"] = "Success"; $output["Status"] = "Success";
} }
return $output; 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 function getUploadPath($type = "unknown", $filename = "hehe"): string
{ {
$type = makePathSafe($type); $type = makePathSafe($type);
$id = makePathSafe($_SESSION["ID"]); $id = makePathSafe($_SESSION["ID"]);
$date = makePathSafe(date("YmdHis")); $date = makePathSafe(date("Y/m/d"));
$filename = makePathSafe($filename); $filename = makePathSafe($filename);
$extension = match ($type) { $extension = match ($type) {
'image' => 'webp', 'image' => 'webp',
default => 'dummy', default => 'dummy',
}; };
if ($extension != "dummy") { if($extension != "dummy") {
$basepath = "uploads/$type/$id/$date"; return "uploads/$type/$id/$date/$filename.$extension";
mkdir($basepath, 755, true); }
return $basepath . "/$filename.$extension"; else {
} else {
return ""; 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;
}

@ -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>

@ -5,16 +5,3 @@
<p>Neoficiálna študentská stránka pre Adlerku</p> <p>Neoficiálna študentská stránka pre Adlerku</p>
</header> </header>
<hr> <hr>
<div class="sliderm" id="exampe-slider">
<div class="sliderm__slider">
<div class="sliderm__slides">
<div class="sliderm__slide">1</div>
<div class="sliderm__slide">2</div>
<div class="sliderm__slide">3</div>
<div class="sliderm__slide">4</div>
<div class="sliderm__slide">5</div>
</div>
</div>
</div>

7
pages/memes/index.html Normal file

@ -0,0 +1,7 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="1" secret="no" page_title="Memes"></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"
]
];

@ -19,10 +19,11 @@ $output = str_replace("__TEMPLATE_FOR_ARTICLE_CONTENT__", $articleTemplate, $out
foreach ($articles as $article){ foreach ($articles as $article){
$articleTitle = htmlspecialchars($article["Title"]); $articleTitle = htmlspecialchars($article["Title"]);
$articleBody = htmlspecialchars($article["Body"]); $articleBody = htmlspecialchars($article["Body"]);
//$articleFileList = $article["FileList"]; $articleFileList = $article["FileList"];
//$articleWrittenBy = $article["WrittenBy"]; $articleWrittenBy = $article["WrittenBy"];
$articleWrittenAt = htmlspecialchars($article["WrittenAt"]); $articleWrittenAt = htmlspecialchars($article["WrittenAt"]);
$articleWrittenByName = htmlspecialchars($article["WrittenByName"]); $articleWrittenByName = htmlspecialchars($article["WrittenByName"]);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_TITLE__", $articleTitle, $articleTemplate); $articleTemplate = str_replace("__TEMPLATE_ARTICLE_TITLE__", $articleTitle, $articleTemplate);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_AUTHOR__", $articleWrittenByName, $articleTemplate); $articleTemplate = str_replace("__TEMPLATE_ARTICLE_AUTHOR__", $articleWrittenByName, $articleTemplate);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_DATE__", $articleWrittenAt, $articleTemplate); $articleTemplate = str_replace("__TEMPLATE_ARTICLE_DATE__", $articleWrittenAt, $articleTemplate);

@ -4,5 +4,3 @@
<h1 class="title">Adlerka Zošit</h1> <h1 class="title">Adlerka Zošit</h1>
</header> </header>
<hr> <hr>
<h2>Čoskoro bude v prevádzke</h2>
<h3>Nájdete(a pridáte) tu poznámky a úlohy zo školy</h3>

@ -3,7 +3,7 @@
<h1>Rozvrh 1.C 2.Skupina</h1> <h1>Rozvrh 1.C 2.Skupina</h1>
</header> </header>
<main> <main>
<table class="rozvrh"> <table>
<tbody> <tbody>
<tr> <tr>
<th>0 (7:10 - 7:55)</th> <th>0 (7:10 - 7:55)</th>

@ -3,7 +3,7 @@
<h1>Rozvrh 1.C 1.Skupina</h1> <h1>Rozvrh 1.C 1.Skupina</h1>
</header> </header>
<main> <main>
<table class="rozvrh"> <table>
<tbody> <tbody>
<tr> <tr>
<th>0 (7:10 - 7:55)</th> <th>0 (7:10 - 7:55)</th>

@ -4,7 +4,7 @@
<h1>Rozvrh 1.D 1.Skupina</h1> <h1>Rozvrh 1.D 1.Skupina</h1>
</header> </header>
<main> <main>
<table class="rozvrh"> <table>
<tbody> <tbody>
<tr> <tr>
<th>0 (7:10 - 7:55)</th> <th>0 (7:10 - 7:55)</th>

@ -14,6 +14,22 @@
<li>Módovaný gameplay bez nutnosti sťahovať módy (niektoré módy však vylepšia zážitok)</li> <li>Módovaný gameplay bez nutnosti sťahovať módy (niektoré módy však vylepšia zážitok)</li>
<li>Jednoduché pripojenie, hostname je <strong>adlerka.top</strong></li> <li>Jednoduché pripojenie, hostname je <strong>adlerka.top</strong></li>
<li>
<strong>Odporúčané módy:</strong>
<ul>
<li>Polymer</li>
<li>CC: Tweaked</li>
<li>More tools</li>
<li>Illager Expansion Recrafted</li>
<li>Simple Voice Chat</li>
<li>Sodium</li>
<li>Just Enough Items</li>
<li>FallingTree</li>
<li>No Chat Reports</li>
</ul>
</li>
<li> <li>
<strong>Super admini:</strong> <strong>Super admini:</strong>
<ul> <ul>

5
pages/smp/map.html Normal file

@ -0,0 +1,5 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="1" secret="no" page_title="Mapa"></page>
<main>
<iframe id="dynmapa" src="https://mc.adlerka.top" allowtransparency="true"></iframe>
</main>

@ -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,27 +2,25 @@
<p id="StatusMessage"></p> <p id="StatusMessage"></p>
<main class="login-file"> <main class="login-file">
<div class="container" id="container"> <div class="container" id="container">
<div class="form-container sign-in" id="sign_in_form"> <div class="form-container sign-up">
<h1>Login</h1> <form>
<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> <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="firstName" id="register_firstName" required placeholder="First name">
<input type="text" name="lastName" id="register_lastName" required placeholder="Last 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="email" name="email" id="register_email" required placeholder="Email">
<input type="password" name="password" id="register_password" required placeholder="Password"> <input type="password" name="password" id="register_password" required placeholder="Password">
<input type="text" name="activationToken" id="register_activationToken" required <input type="text" name="activationToken" id="register_activationToken" required
placeholder="Activation Token"> placeholder="Activation Token">
<button type="button">Register</button> <button type="button" onclick="register()">Register</button>
</form>
</div>
<div class="form-container sign-in">
<h1>Login</h1>
<form>
<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> </form>
<p onclick="toggleRegister()">Already have an account</p>
</div> </div>
</div> </div>
</main> </main>

@ -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,11 +1,9 @@
<div class="logo"> <div class="logo">
<a href="/home/index">
<picture id="standard-logo"> <picture id="standard-logo">
<source media="(min-width:4200px)" srcset="/assets/images/adlerka_256.png"> <source media="(min-width:4200px)" srcset="/assets/images/adlerka_256.png">
<source media="(min-width:2100px)" srcset="/assets/images/adlerka_128.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;"> <img src="/assets/images/adlerka_64.png" alt="Adlerka logo" style="width:auto;">
</picture> </picture>
</a>
</div> </div>
<i class="ri-menu-line" id="toggle_button"></i> <i class="ri-menu-line" id="toggle_button"></i>
<ul id="navsite_list"> <ul id="navsite_list">

@ -15,10 +15,8 @@
<div id="articlecreatecontainer" class="hidden"> <div id="articlecreatecontainer" class="hidden">
<div id="articlecreate"> <div id="articlecreate">
<input type="text" placeholder="Article Title" id="articletitleinput"><br> <input type="text" placeholder="Article Title" id="articletitleinput"><br>
<textarea id="articlebodyinput" placeholder="Article Body" rows="10" cols="80"></textarea><br> <textarea id="articlebodyinput" placeholder="Article Body" rows="20" 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"><br>
<input type="number" id="articleprivilegeinput" min="1" value="1" step="1"> <button id="articlesubmit" onclick="submitarticle()">Add</button>
<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>
</div> </div>

@ -5,14 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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/fonts/remixicon/remixicon.css">
<link rel="stylesheet" href="/assets/3rdparty/pico.min.css"> <link rel="stylesheet" href="/assets/3rdparty/pico.min.css">
<link href="/assets/3rdparty/sliderm.css" rel="stylesheet">
<link rel="stylesheet" href="/assets/style.css"> <link rel="stylesheet" href="/assets/style.css">
<link rel="icon" href="/assets/images/favicon.png" type="image/png"> <link rel="icon" href="/assets/images/favicon.png" type="image/png">
__TEMPLATE__DYNAMIC__SCRIPT__ __TEMPLATE__DYNAMIC__SCRIPT__
__TEMPLATE__DYNAMIC__STYLE__ __TEMPLATE__DYNAMIC__STYLE__
<script async src="https://umami.brn.systems/script.js" data-website-id="95e93885-5c19-4cab-ba9b-2f746a316a2a"></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> <script async src="/assets/script.js"></script>
<script async src="/assets/3rdparty/sliderm.js"></script>
<title>__TEMPLATE_PAGE_TITLE__</title> <title>__TEMPLATE_PAGE_TITLE__</title>
__TEMPLATE_SEO_STUFF__ __TEMPLATE_SEO_STUFF__
</head> </head>

@ -1,14 +1,12 @@
<!-- Centralized Status Message --> <!-- Centralized Status Message -->
<p id="StatusMessage"></p> <p id="StatusMessage"></p>
<div id="user-settings"> <div id="user-settings">
<button type="button" onclick="logout()">Logout</button> <button type="button" onclick="logout()">Logout</button><br>
<br>
<div class="form-container" id="updateUserProfileForm"> <div class="form-container" id="updateUserProfileForm">
<h1>Update User</h1> <h1>Update User</h1>
<h2>Profile</h2> <h2>Profile</h2>
<div class="form-content" id="profile-form">
<label for="updateFirstName">First Name:</label> <label for="updateFirstName">First Name:</label>
<input type="text" id="updateFirstName" name="updateFirstName" required><br> <input type="text" id="updateFirstName" name="updateFirstName" required><br>
@ -22,20 +20,20 @@
<input type="text" id="updateMinecraftNick" name="updateMinecraftNick" required><br> <input type="text" id="updateMinecraftNick" name="updateMinecraftNick" required><br>
<button type="button" onclick="updateUserProfile()">Update Profile</button> <button type="button" onclick="updateUserProfile()">Update Profile</button>
</div>
<br><br> <br><br>
<h2>Email</h2> <h2>Email</h2>
<div class="form-content" id="email-form">
<label for="updateNewEmail">New Email:</label> <label for="updateNewEmail">New Email:</label>
<input type="email" id="updateNewEmail" name="updateNewEmail" required><br> <input type="email" id="updateNewEmail" name="updateNewEmail" required><br>
<button type="button" onclick="updateEmail()">Update Email</button> <button type="button" onclick="updateEmail()">Update Email</button>
</div>
<br><br> <br><br>
<h2>Password</h2> <h2>Password</h2>
<div class="form-content" id="password-form">
<label for="changeOldPassword">Old Password:</label> <label for="changeOldPassword">Old Password:</label>
<input type="password" id="changeOldPassword" name="changeOldPassword" required><br> <input type="password" id="changeOldPassword" name="changeOldPassword" required><br>
@ -44,7 +42,6 @@
<button type="button" onclick="changePassword()">Change Password</button> <button type="button" onclick="changePassword()">Change Password</button>
</div> </div>
</div>
</div> </div>
<hr> <hr>