Commit 0f10aad0 authored by jan.koester's avatar jan.koester
Browse files

test

parent 334e5f60
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@
    <body>
       <div id="header">
	 <!-- header image -->
         <img alt="headerimage" width="1280" height="150" id="banner" src="${PREFIX}/theme/public/header.webp" >
         <img alt="headerimage" width="1280" height="150" id="banner" src="${LOGO_URL}" >
       </div>
       <!-- Navbar-->
       <div id="headnav">
+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ target_link_libraries(blogidev PUBLIC
    dbpp::dbpp
    uuidp::uuidp
    tinyxml2::tinyxml2
    mediadb_client
    cjson
)

if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
+1 −1
Original line number Diff line number Diff line
@@ -1173,7 +1173,7 @@ void blogi::Blogi::Settings(libhttppp::HttpRequest& curreq,const int tid,const s
            jformdata = SettingsMail(*PlgArgs->database[tid], curreq, sessiondid);
        }else if(url.compare(0, PlgArgs->config->buildurl("settings/theme").length(),
           PlgArgs->config->buildurl("settings/theme")) == 0){
            jformdata = PlgArgs->theme->SettingsTheme(tid, curreq);
            jformdata = PlgArgs->theme->SettingsTheme(tid, curreq, sessiondid);
        }else if(url.compare(0, PlgArgs->config->buildurl("settings/language").length(),
           PlgArgs->config->buildurl("settings/language")) == 0){
            jformdata = SettingsLanguage(*PlgArgs->database[tid], curreq);
+186 −3
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@
#include "conf.h"
#include "compress.h"
#include "i18n.h"
#include "session.h"

blogi::Theme::Theme(blogi::ThemeConfig& config,bool formated) : _Config(config){
    auto trimlines = [&] (const std::string in){
@@ -163,6 +164,31 @@ blogi::Theme::~Theme(){
    // Destructor implementation
}

void blogi::Theme::ensureMediaClient(const int tid, const std::string &token) {
    if (_MdbClients.empty())
        _MdbClients.resize(_Config.TDatabase.size());
    std::string mdbUrl;
    try { getConfig(*_Config.TDatabase[tid], "MEDIADB_URL", mdbUrl); } catch (...) {}
    if (mdbUrl.empty())
        return;
    if (!_MdbClients[tid])
        _MdbClients[tid] = std::make_unique<mediadb::Client>(mdbUrl, false, true);
    if (!token.empty())
        _MdbClients[tid]->set_token(token);
}

std::string blogi::Theme::getLogoUrl(const int tid) const {
    std::string mediaId, mediaExt;
    try { getConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", mediaId); } catch (...) {}
    try { getConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_EXT", mediaExt); } catch (...) {}
    if (!mediaId.empty()) {
        if (mediaExt.empty()) mediaExt = "webp";
        return _Config.config.buildurl("media/getimage/" + mediaId + "." + mediaExt);
    }
    // Fallback to theme default
    return _Config.config.getprefix() + "/theme/public/header.webp";
}

void blogi::Theme::renderPage(const int tid,const char *name,libhtmlpp::HtmlPage& page, libhtmlpp::HtmlElement &index){
    std::string htmlfile=_Config.Theme;
#ifdef Windows
@@ -193,6 +219,7 @@ void blogi::Theme::renderPage(const int tid,const char *name,libhtmlpp::HtmlPage
    std::map<std::string, std::string> variable_map = {
        {"PREFIX", _Config.config.getprefix()},
        {"SITEURL", _Config.config.getsiteurl()},
        {"LOGO_URL", getLogoUrl(tid)},
    };

    // Inline CSS content as template variables to eliminate render-blocking requests
@@ -553,9 +580,19 @@ void blogi::Theme::injectThemeColors(const int tid, libhtmlpp::HtmlElement *inde
    head->appendChild(css.parse());
}

json_object* blogi::Theme::SettingsTheme(const int tid, libhttppp::HttpRequest &req){
json_object* blogi::Theme::SettingsTheme(const int tid, libhttppp::HttpRequest &req, const std::string &sessionid){
    bool saved = false;
    bool reset = false;
    std::string logoMsg;

    // Get auth token for mediadb
    std::string authToken;
    if (!sessionid.empty()) {
        try {
            Session sess(*_Config.TDatabase[tid]);
            sess.getSessionData(sessionid, "authid", authToken);
        } catch (...) {}
    }

    // Handle form POST — save dark and light color fields
    libhttppp::HttpForm form;
@@ -564,10 +601,110 @@ json_object* blogi::Theme::SettingsTheme(const int tid, libhttppp::HttpRequest &
    const char *suffixes[] = {"BG","SURFACE","SURFACE_A","TEXT","TEXT_MUTED","ACCENT","ACCENT_DARK","BORDER","BG_ALT","ERROR","ERROR_BG","ERROR_TEXT","SUCCESS_BG","SUCCESS_TEXT","NAV_ACTIVE","NAV_HOVER","NAV_BORDER"};
    const char *profiles[] = {"dark", "light"};

    // Check for reset request
    // Check for reset / logo delete
    for(const auto &cur : form.urlData()){
        if(cur.key == "theme_reset" && cur.value == "1"){
            reset = true;
        }
        if(cur.key == "delete_logo" && !cur.value.empty()){
            // Delete logo from mediadb and clear config
            std::string oldMediaId;
            try { getConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", oldMediaId); } catch (...) {}
            if (!oldMediaId.empty()) {
                try {
                    ensureMediaClient(tid, authToken);
                    if (_MdbClients[tid])
                        _MdbClients[tid]->delete_media(oldMediaId);
                } catch (...) {}
                setConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", "");
                setConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_EXT", "");
            }
            logoMsg = "Logo deleted!";
            saved = true;
        }
    }

    // Handle logo file upload (multipart)
    for (const auto &part : form.multipartData()) {
        std::string fieldName, filename;
        for (const auto &disp : part.dispositions) {
            if (disp.key == "name") fieldName = disp.value;
            if (disp.key == "filename") filename = disp.value;
        }
        if (fieldName == "theme_logo" && !filename.empty() && !part.value.empty()) {
            try {
                ensureMediaClient(tid, authToken);
                if (!_MdbClients[tid]) {
                    logoMsg = "MediaDB not configured (set MEDIADB_URL)";
                    break;
                }

                // Find or create a "theme" album in the first available store
                std::string albumId;
                std::string mdbStores;
                try { getConfig(*_Config.TDatabase[tid], "MEDIADB_STORES", mdbStores); } catch (...) {}

                // Parse first store ID from JSON array
                std::string storeId;
                if (!mdbStores.empty()) {
                    json_object *jstores = json_tokener_parse(mdbStores.c_str());
                    if (jstores && json_object_is_type(jstores, json_type_array) && json_object_array_length(jstores) > 0) {
                        storeId = json_object_get_string(json_object_array_get_idx(jstores, 0));
                    }
                    if (jstores) json_object_put(jstores);
                }

                if (storeId.empty()) {
                    // Try single store config
                    try { getConfig(*_Config.TDatabase[tid], "MEDIADB_STORE", storeId); } catch (...) {}
                }

                if (storeId.empty()) {
                    // List stores and use first one
                    auto stores = _MdbClients[tid]->list_stores();
                    if (!stores.empty()) storeId = stores[0].id;
                }

                if (storeId.empty()) {
                    logoMsg = "No MediaDB store available";
                    break;
                }

                // Find or create "blogi-theme" album
                auto albums = _MdbClients[tid]->list_albums(storeId);
                for (const auto &a : albums) {
                    if (a.name == "blogi-theme") { albumId = a.id; break; }
                }
                if (albumId.empty()) {
                    auto newAlbum = _MdbClients[tid]->create_album(storeId, "blogi-theme");
                    albumId = newAlbum.id;
                }

                // Delete old logo if exists
                std::string oldMediaId;
                try { getConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", oldMediaId); } catch (...) {}
                if (!oldMediaId.empty()) {
                    try { _MdbClients[tid]->delete_media(oldMediaId); } catch (...) {}
                }

                // Upload
                std::vector<std::uint8_t> payload(
                    reinterpret_cast<const std::uint8_t*>(part.value.data()),
                    reinterpret_cast<const std::uint8_t*>(part.value.data()) + part.value.size());
                auto mi = _MdbClients[tid]->upload(albumId, filename, payload);

                // Determine extension
                std::string ext = "webp";
                size_t dp = mi.filename.rfind('.');
                if (dp != std::string::npos) ext = mi.filename.substr(dp + 1);

                setConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", mi.id);
                setConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_EXT", ext);
                logoMsg = "Logo uploaded!";
                saved = true;
            } catch (std::exception &e) {
                logoMsg = std::string("Upload failed: ") + e.what();
            }
            break;
        }
    }
@@ -621,9 +758,15 @@ json_object* blogi::Theme::SettingsTheme(const int tid, libhttppp::HttpRequest &
    json_object *jroot = json_object_new_object();
    json_object_object_add(jroot, "title", json_object_new_string("Theme Settings"));

    if(saved){
    if(saved && !logoMsg.empty()){
        json_object_object_add(jroot, "message", json_object_new_string(logoMsg.c_str()));
        json_object_object_add(jroot, "message_type", json_object_new_string("success"));
    } else if(saved){
        json_object_object_add(jroot, "message", json_object_new_string("Saved!"));
        json_object_object_add(jroot, "message_type", json_object_new_string("success"));
    } else if(!logoMsg.empty()){
        json_object_object_add(jroot, "message", json_object_new_string(logoMsg.c_str()));
        json_object_object_add(jroot, "message_type", json_object_new_string("error"));
    }
    if(reset){
        json_object_object_add(jroot, "message", json_object_new_string("Reset to defaults!"));
@@ -633,9 +776,49 @@ json_object* blogi::Theme::SettingsTheme(const int tid, libhttppp::HttpRequest &
    json_object *jform = json_object_new_object();
    json_object_object_add(jform, "method", json_object_new_string("POST"));
    json_object_object_add(jform, "action", json_object_new_string(req.getRequestURL().c_str()));
    json_object_object_add(jform, "enctype", json_object_new_string("multipart/form-data"));

    json_object *jfields = json_object_new_array();

    // Section header: Logo
    {
        std::string logoUrl = getLogoUrl(tid);
        std::string curMediaId;
        try { getConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", curMediaId); } catch (...) {}

        std::string logoLabel = "Logo";
        if (!curMediaId.empty())
            logoLabel += " &mdash; <img src=\"" + logoUrl + "\" style=\"max-height:60px;max-width:200px;vertical-align:middle;margin-left:8px;\" />";
        else
            logoLabel += " (using theme default)";

        json_object *jf = json_object_new_object();
        json_object_object_add(jf, "type", json_object_new_string("heading"));
        json_object_object_add(jf, "label", json_object_new_string(logoLabel.c_str()));
        json_object_array_add(jfields, jf);
    }
    // Logo file upload
    {
        json_object *jf = json_object_new_object();
        json_object_object_add(jf, "name", json_object_new_string("theme_logo"));
        json_object_object_add(jf, "type", json_object_new_string("file"));
        json_object_object_add(jf, "label", json_object_new_string("Upload Logo"));
        json_object_array_add(jfields, jf);
    }
    // Delete logo checkbox (only if a logo is set)
    {
        std::string curMediaId;
        try { getConfig(*_Config.TDatabase[tid], "THEME_LOGO_MEDIA_ID", curMediaId); } catch (...) {}
        if (!curMediaId.empty()) {
            json_object *jf = json_object_new_object();
            json_object_object_add(jf, "name", json_object_new_string("delete_logo"));
            json_object_object_add(jf, "type", json_object_new_string("checkbox"));
            json_object_object_add(jf, "label", json_object_new_string("Delete Logo (revert to theme default)"));
            json_object_object_add(jf, "value", json_object_new_string("1"));
            json_object_array_add(jfields, jf);
        }
    }

    // Section header: Dark
    {
        json_object *jf = json_object_new_object();
+13 −5
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <string>
#include <vector>
#include <memory>

#include <netplus/socket.h>
#include <netplus/connection.h>
@@ -36,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <dbpp/database.h>
#include <json-c/json.h>
#include <mediadb/client.h>

#include "conf.h"
#include "auth.h"
@@ -85,12 +87,18 @@ namespace blogi {
        /// Build a <style> block with CSS custom properties from the active theme profile
        void injectThemeColors(const int tid, libhtmlpp::HtmlElement *index);

        /// Settings UI for theme colors (returns JSON form descriptor)
        json_object* SettingsTheme(const int tid, libhttppp::HttpRequest &req);
        /// Settings UI for theme colors + logo upload (returns JSON form descriptor)
        json_object* SettingsTheme(const int tid, libhttppp::HttpRequest &req, const std::string &sessionid);

        /// Get the logo URL (uploaded or theme default)
        std::string getLogoUrl(const int tid) const;

    private:
        void ensureMediaClient(const int tid, const std::string &token = "");

        ThemeConfig                                        _Config;
        std::vector<ThemeFiles>                             _PublicFiles;
        bool                                               _Formated;
        mutable std::vector<std::unique_ptr<mediadb::Client>> _MdbClients;
    };
};