#include "BlogView.h" #include "BlogLoginWidget.h" #include "EditUsers.h" #include "PostView.h" #include "model/BlogSession.h" #include "model/Post.h" #include "model/Tag.h" #include #include #include #include #include #include #include static int try_stoi(const std::string &v) { std::size_t pos; auto result = std::stoi(v, &pos); if (pos != v.length()) throw std::invalid_argument("stoi() of " + v + " failed"); return result; } class BlogImpl : public Wt::WContainerWidget { public: BlogImpl(const std::string &basePath, Wt::Dbo::SqlConnectionPool &connectionPool, const std::string &rssFeedUrl, BlogView *blogView) : m_basePath(basePath), m_rssFeedUrl(rssFeedUrl), m_session(connectionPool) { Wt::WApplication::instance()->internalPathChanged().connect(this, &BlogImpl::handlePathChange); m_loginStatus = this->addWidget(std::make_unique(tr("blog-login-status"))); m_panel = this->addWidget(std::make_unique()); m_items = this->addWidget(std::make_unique()); m_session.login().changed().connect(this, &BlogImpl::onUserChanged); auto loginWidget = std::make_unique(m_session, basePath); m_loginWidget = loginWidget.get(); m_loginWidget->hide(); auto loginLink = std::make_unique(tr("login")); auto lPtr = loginLink.get(); loginLink->setStyleClass("link"); loginLink->clicked().connect(m_loginWidget, &BlogLoginWidget::show); loginLink->clicked().connect(lPtr, &WWidget::hide); auto registerLink = std::make_unique(tr("Wt.Auth.register")); registerLink->setStyleClass("link"); registerLink->clicked().connect(m_loginWidget, &BlogLoginWidget::registerNewUser); auto archiveLink = std::make_unique(Wt::WLink(Wt::LinkType::InternalPath, m_basePath + "all"), tr("archive")); m_loginStatus->bindWidget("login", std::move(loginWidget)); m_loginStatus->bindWidget("login-link", std::move(loginLink)); m_loginStatus->bindWidget("register-link", std::move(registerLink)); m_loginStatus->bindString("feed-url", m_rssFeedUrl); m_loginStatus->bindWidget("archive-link", std::move(archiveLink)); onUserChanged(); m_loginWidget->processEnvironment(); } protected: void handlePathChange(const std::string &) { Wt::WApplication *app = Wt::WApplication::instance(); if (app->internalPathMatches(m_basePath)) { Wt::Dbo::Transaction t(m_session); std::string path = app->internalPathNextPart(m_basePath); m_items->clear(); if (m_users) { m_users = 0; } if (path.empty()) showPosts(m_session .find("where state = ? " "order by date desc " "limit 10") .bind(Post::Published), m_items); else if (path == "author") { std::string author = app->internalPathNextPart(m_basePath + path + '/'); Wt::Dbo::ptr user = findUser(author); if (user) showPosts(user); else showError(tr("blog-no-author").arg(author)); } else if (path == "edituser") { editUser(app->internalPathNextPart(m_basePath + path + '/')); } else if (path == "all") { showArchive(m_items); } else { std::string remainder = app->internalPath().substr(m_basePath.length()); showPostsByDateTopic(remainder, m_items); } t.commit(); } } void showArchive(WContainerWidget *parent) { static const char *dateFormat = "MMMM yyyy"; parent->addWidget(std::make_unique(tr("archive-title"))); Posts posts = m_session.find("order by date desc"); Wt::WDateTime formerDate; for (auto post : posts) { if (post->state != Post::Published) continue; if (formerDate.isNull() || yearMonthDiffer(formerDate, post->date)) { Wt::WText *title = parent->addWidget(std::make_unique(post->date.date().toString(dateFormat))); title->setStyleClass("archive-month-title"); } Wt::WAnchor *a = parent->addWidget(std::make_unique( Wt::WLink(Wt::LinkType::InternalPath, m_basePath + post->permaLink()), post->title)); a->setInline(false); formerDate = post->date; } } bool yearMonthDiffer(const Wt::WDateTime &dt1, const Wt::WDateTime &dt2) { return dt1.date().year() != dt2.date().year() || dt1.date().month() != dt2.date().month(); } Wt::Dbo::ptr findUser(const std::string &name) { return m_session.find("where name = ?").bind(name); } bool checkLoggedIn() { if (m_session.user()) return true; m_panel->show(); if (!m_mustLoginWarning) { m_mustLoginWarning = m_panel->addWidget(std::make_unique(tr("blog-mustlogin"))); } m_panel->setCurrentWidget(m_mustLoginWarning); return false; } bool checkAdministrator() { if (m_session.user() && (m_session.user()->role == User::Admin)) return true; m_panel->show(); if (!m_mustBeAdministratorWarning) { m_mustBeAdministratorWarning = m_panel->addWidget(std::make_unique(tr("blog-mustbeadministrator"))); } m_panel->setCurrentWidget(m_mustBeAdministratorWarning); return false; } void editUser(const std::string &ids) { if (!checkLoggedIn()) return; if (!checkAdministrator()) return; Wt::Dbo::dbo_traits::IdType id = User::stringToId(ids); m_panel->show(); try { Wt::Dbo::Transaction t(m_session); Wt::Dbo::ptr target(m_session.load(id)); if (!m_userEditor) { m_userEditor = m_panel->addWidget(std::make_unique(m_session)); } m_userEditor->switchUser(target); m_panel->setCurrentWidget(m_userEditor); } catch (Wt::Dbo::ObjectNotFoundException &) { if (!m_invalidUser) { m_invalidUser = m_panel->addWidget(std::make_unique(tr("blog-invaliduser"))); } m_panel->setCurrentWidget(m_invalidUser); } } void showPostsByDateTopic(const std::string &path, WContainerWidget *parent) { std::vector parts; boost::split(parts, path, boost::is_any_of("/")); Wt::WDate lower, upper; try { int year = try_stoi(parts[0]); if (parts.size() > 1) { int month = try_stoi(parts[1]); if (parts.size() > 2) { int day = try_stoi(parts[2]); lower.setDate(year, month, day); upper = lower.addDays(1); } else { lower.setDate(year, month, 1); upper = lower.addMonths(1); } } else { lower.setDate(year, 1, 1); upper = lower.addYears(1); } } catch (std::invalid_argument &) { showError(tr("blog-no-post")); return; } Posts posts = m_session .find("where date >= ? " "and date < ? " "and (state = ? or author_id = ?)") .bind(Wt::WDateTime(lower)) .bind(Wt::WDateTime(upper)) .bind(Post::Published) .bind(m_session.user().id()); if (parts.size() > 3) { std::string title = parts[3]; for (auto post : posts) if (post->titleToUrl() == title) { showPost(post, PostView::Detail, parent); return; } showError(tr("blog-no-post")); } else { showPosts(posts, parent); } } void showPosts(Wt::Dbo::ptr user) { showPosts(user->latestPosts(), m_items); } void showPosts(const Posts &posts, WContainerWidget *parent) { for (auto post : posts) showPost(post, PostView::Brief, parent); } void onUserChanged() { if (m_session.login().loggedIn()) loggedIn(); else loggedOut(); } void editUsers() { m_panel->show(); if (!m_users) { m_users = m_panel->addWidget(std::make_unique(m_session, m_basePath)); bindPanelTemplates(); } m_panel->setCurrentWidget(m_users); } BlogSession &session() { return m_session; } void loggedIn() { Wt::WApplication::instance()->changeSessionId(); refresh(); m_loginStatus->resolveWidget("login")->show(); m_loginStatus->resolveWidget("login-link")->hide(); m_loginStatus->resolveWidget("register-link")->hide(); auto profileLink = std::make_unique(tr("profile")); profileLink->setStyleClass("link"); profileLink->clicked().connect(this, &BlogImpl::editProfile); Wt::Dbo::ptr user = session().user(); if (user->role == User::Admin) { auto editUsersLink = std::make_unique(tr("edit-users")); editUsersLink->setStyleClass("link"); editUsersLink->clicked().connect(this, &BlogImpl::editUsers); m_loginStatus->bindWidget("userlist-link", std::move(editUsersLink)); auto authorPanelLink = std::make_unique(tr("author-post")); authorPanelLink->setStyleClass("link"); authorPanelLink->clicked().connect(this, &BlogImpl::authorPanel); m_loginStatus->bindWidget("author-panel-link", std::move(authorPanelLink)); } else { m_loginStatus->bindEmpty("userlist-link"); m_loginStatus->bindEmpty("author-panel-link"); } m_loginStatus->bindWidget("profile-link", std::move(profileLink)); bindPanelTemplates(); } void loggedOut() { m_loginStatus->bindEmpty("profile-link"); m_loginStatus->bindEmpty("author-panel-link"); m_loginStatus->bindEmpty("userlist-link"); m_loginStatus->resolveWidget("login")->hide(); m_loginStatus->resolveWidget("login-link")->show(); m_loginStatus->resolveWidget("register-link")->show(); refresh(); m_panel->hide(); } void editProfile() { m_loginWidget->letUpdatePassword(m_session.login().user(), true); } void showError(const Wt::WString &msg) { m_items->addWidget(std::make_unique(msg)); } void authorPanel() { m_panel->show(); if (!m_authorPanel) { m_authorPanel = m_panel->addWidget(std::make_unique(tr("blog-author-panel"))); bindPanelTemplates(); } m_panel->setCurrentWidget(m_authorPanel); } void showPost(const Wt::Dbo::ptr post, PostView::RenderType type, Wt::WContainerWidget *parent) { parent->addWidget(std::make_unique(m_session, m_basePath, post, type)); } void newPost() { Wt::Dbo::Transaction t(m_session); authorPanel(); WContainerWidget *unpublishedPosts = m_authorPanel->resolve("unpublished-posts"); Wt::Dbo::ptr post(std::make_unique()); Post *p = post.modify(); p->state = Post::Unpublished; p->author = m_session.user(); p->title = "Title"; p->briefSrc = "Brief ..."; p->bodySrc = "Body ..."; showPost(post, PostView::Edit, unpublishedPosts); t.commit(); } void bindPanelTemplates() { if (!m_session.user()) return; Wt::Dbo::Transaction t(m_session); if (m_authorPanel) { auto newPost = std::make_unique(tr("new-post")); newPost->clicked().connect(this, &BlogImpl::newPost); auto unpublishedPosts = std::make_unique(); showPosts(m_session.user()->allPosts(Post::Unpublished), unpublishedPosts.get()); m_authorPanel->bindString("user", m_session.user()->name); m_authorPanel->bindInt("unpublished-count", (int)m_session.user()->allPosts(Post::Unpublished).size()); m_authorPanel->bindInt("published-count", (int)m_session.user()->allPosts(Post::Published).size()); m_authorPanel->bindWidget("new-post", std::move(newPost)); m_authorPanel->bindWidget("unpublished-posts", std::move(unpublishedPosts)); } t.commit(); } private: std::string m_basePath, m_rssFeedUrl; BlogSession m_session; BlogLoginWidget *m_loginWidget = nullptr; Wt::WStackedWidget *m_panel = nullptr; Wt::WTemplate *m_authorPanel = nullptr; EditUsers *m_users = nullptr; EditUser *m_userEditor = nullptr; Wt::WTemplate *m_mustLoginWarning = nullptr; Wt::WTemplate *m_mustBeAdministratorWarning = nullptr; Wt::WTemplate *m_invalidUser = nullptr; Wt::WTemplate *m_loginStatus = nullptr; WContainerWidget *m_items = nullptr; }; BlogView::BlogView(const std::string &basePath, Wt::Dbo::SqlConnectionPool &db, const std::string &rssFeedUrl) : WCompositeWidget(), m_userChanged() { m_impl = setImplementation(std::make_unique(basePath, db, rssFeedUrl, this)); }