Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/Controller.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ component extends="wheels.Controller" {
// Shared business logic across multiple controllers
public function getBlogBySlug(required string slug) {
return model("Blog").findOne(
where="blog_posts.slug = '#arguments.slug#' AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.slug = '#arguments.slug#' AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
include="User,PostStatus",
cache=10
);
Expand Down
67 changes: 36 additions & 31 deletions app/controllers/web/BlogController.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ component extends="app.Controllers.Controller" {
private function getBlogsByAuthor(required authorId, numeric page=1, numeric perPage=6, boolean isInfiniteScroll=false) {
var result = {
query = model("Blog").findAll(
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#' AND blog_posts.createdBy = #arguments.authorId#",
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#' AND blog_posts.createdBy = #arguments.authorId#",
include="User",
order="COALESCE(post_created_date, blog_posts.createdat) DESC",
page = arguments.page,
Expand All @@ -275,7 +275,7 @@ component extends="app.Controllers.Controller" {
};

result.totalCount = model("Blog").count(
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#' AND blog_posts.createdBy = #arguments.authorId#"
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#' AND blog_posts.createdBy = #arguments.authorId#"
);
result.hasMore = (page * perPage) < result.totalCount;

Expand Down Expand Up @@ -318,7 +318,7 @@ component extends="app.Controllers.Controller" {
if (len(trim(searchTerm))) {
var searchPattern = "%#searchTerm#%";
var query = model("blog").findAll(
where="blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#' AND (blog_posts.slug LIKE '#searchPattern#' OR blog_posts.title LIKE '#searchPattern#' OR blog_posts.content LIKE '#searchPattern#' OR fullname LIKE '#searchPattern#' OR email LIKE '#searchPattern#')",
where="blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#' AND (blog_posts.slug LIKE '#searchPattern#' OR blog_posts.title LIKE '#searchPattern#' OR blog_posts.content LIKE '#searchPattern#' OR fullname LIKE '#searchPattern#' OR email LIKE '#searchPattern#')",
include="User, PostStatus, PostType",
order = "COALESCE(post_created_date, blog_posts.createdat) DESC",
page = page,
Expand All @@ -328,7 +328,7 @@ component extends="app.Controllers.Controller" {
if (isInfiniteScroll) {
totalCount = model("blog").count(
include="User, PostStatus, PostType",
where="blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#' AND (blog_posts.slug LIKE '#searchPattern#' OR blog_posts.title LIKE '#searchPattern#' OR blog_posts.content LIKE '#searchPattern#' OR fullname LIKE '#searchPattern#' OR email LIKE '#searchPattern#')"
where="blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#' AND (blog_posts.slug LIKE '#searchPattern#' OR blog_posts.title LIKE '#searchPattern#' OR blog_posts.content LIKE '#searchPattern#' OR fullname LIKE '#searchPattern#' OR email LIKE '#searchPattern#')"
);
hasMore = (page * perPage) < totalCount;
isSearched = true;
Expand Down Expand Up @@ -771,7 +771,7 @@ component extends="app.Controllers.Controller" {
public function feed() {
// Fetch all blogs
blogPosts = model("Blog").findAll(
where="blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
include="User",
order="postDate DESC",
cache=10
Expand Down Expand Up @@ -825,7 +825,7 @@ component extends="app.Controllers.Controller" {
private function getAllBlogs(numeric page=1, numeric perPage=6, boolean isInfiniteScroll=false) {
var result = {
query = model("Blog").findAll(
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
include="User",
order="COALESCE(post_created_date, blog_posts.createdat) DESC",
page = arguments.page,
Expand All @@ -837,7 +837,7 @@ component extends="app.Controllers.Controller" {
};

result.totalCount = model("Blog").count(
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
cache = 5
);
result.hasMore = (page * perPage) < result.totalCount;
Expand All @@ -852,7 +852,7 @@ component extends="app.Controllers.Controller" {

var result = {
query = model("Blog").findAll(
where="blog_posts.post_created_date BETWEEN '#startdate#' AND '#enddate#' AND blog_posts.status='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.post_created_date BETWEEN '#startdate#' AND '#enddate#' AND blog_posts.status='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
order="postCreatedDate DESC",
include="User",
returnAs="query",
Expand All @@ -864,7 +864,7 @@ component extends="app.Controllers.Controller" {
};

result.totalCount = model("Blog").count(
where="blog_posts.post_created_date BETWEEN '#startdate#' AND '#enddate#' AND blog_posts.status='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'"
where="blog_posts.post_created_date BETWEEN '#startdate#' AND '#enddate#' AND blog_posts.status='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'"
);
result.hasMore = (page * perPage) < result.totalCount;

Expand All @@ -886,7 +886,7 @@ component extends="app.Controllers.Controller" {

var result = {
query = model("Blog").findAll(
where="blog_posts.id IN (#blogIdList#) AND categoryId = #category.id# AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.id IN (#blogIdList#) AND categoryId = #category.id# AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
order="createdAt DESC",
include="User,BlogCategory",
returnAs="query",
Expand All @@ -898,7 +898,7 @@ component extends="app.Controllers.Controller" {
};

result.totalCount = model("Blog").count(
where="blog_posts.id IN (#blogIdList#) AND categoryId = #category.id# AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.id IN (#blogIdList#) AND categoryId = #category.id# AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
include="User,BlogCategory"
);
result.hasMore = (page * perPage) < result.totalCount;
Expand Down Expand Up @@ -926,7 +926,7 @@ component extends="app.Controllers.Controller" {

var result = {
query = model("Blog").findAll(
where="blog_posts.id IN (#blogIds#) AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'",
where="blog_posts.id IN (#blogIds#) AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'",
order="createdAt DESC",
include="User",
returnAs="query",
Expand All @@ -937,7 +937,7 @@ component extends="app.Controllers.Controller" {
totalCount = 0
};

result.totalCount = model("Blog").count(where="blog_posts.id IN (#blogIds#) AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#'");
result.totalCount = model("Blog").count(where="blog_posts.id IN (#blogIds#) AND blog_posts.status ='Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#'");
result.hasMore = (page * perPage) < result.totalCount;

return result;
Expand All @@ -958,24 +958,29 @@ component extends="app.Controllers.Controller" {
slug = rereplace(slug, "-+", "-", "all");
blogData.slug = slug;

// Always set postCreatedDate to UTC now (submission time, UTC)
blogData.postCreatedDate = toUTC(now());

// Determine status based on draft flag and user role
// Handle publishedAt and status
if (blogData.isdraft eq 1) {
blogData.statusId = 1; // Draft
// Draft: publishedAt is NULL
blogData.statusId = 1;
blogData.status = "";
blogData.publishedAt = "";
blogData.publishedAt = javaCast("null", "");
} else if (isUserAdmin()) {
// Auto-approve and publish for admin users
// Admin: auto-approve and publish
blogData.statusId = 2;
blogData.status = "Approved";
if (structKeyExists(blogData, "postCreatedDate") && !isNull(blogData.postCreatedDate) && !isEmpty(blogData.postCreatedDate)) {
blogData.publishedAt = blogData.postCreatedDate;
if (structKeyExists(blogData, "publishedAtInput") && len(blogData.publishedAtInput)) {
// User supplied a scheduled date (local time)
blogData.publishedAt = toSafeUTC(blogData.publishedAtInput, blogData.userTimezone);
} else {
blogData.publishedAt = now();
blogData.postCreatedDate = now();
// No date: publish now in UTC
blogData.publishedAt = toUTC(now());
}
} else {
blogData.statusId = 2; // Under Review
// Non-admin: under review, publishedAt is NULL
blogData.statusId = 2;
blogData.status = "";
blogData.publishedAt = "";
}
Expand All @@ -992,8 +997,12 @@ component extends="app.Controllers.Controller" {
blog.statusId = blogData.statusId;
blog.postTypeId = blogData.postTypeId;
blog.slug = blogData.slug;
blog.updatedAt = now();
blog.updatedAt = toUTC(now());
blog.updatedBy = GetSignedInUserId();
// Only update publishedAt if explicitly provided
if (structKeyExists(blogData, "publishedAtInput") && len(blogData.publishedAtInput)) {
blog.publishedAt = toSafeUTC(blogData.publishedAtInput, blogData.userTimezone);
}
blog.save();

response.blogId = blog.id;
Expand All @@ -1016,20 +1025,16 @@ component extends="app.Controllers.Controller" {
newBlog.statusId = blogData.statusId;
newBlog.postTypeId = blogData.postTypeId;
newBlog.coverImagePath = blogData.coverImagePath;
newBlog.createdAt = now();
newBlog.updatedAt = now();
newBlog.createdAt = toUTC(now());
newBlog.updatedAt = toUTC(now());
newBlog.createdBy = GetSignedInUserId();

// Set approval status fields for admin auto-approval
if (structKeyExists(blogData, "status")) {
newBlog.status = blogData.status;
}
if (structKeyExists(blogData, "postCreatedDate") && !isNull(blogData.postCreatedDate) && !isEmpty(blogData.postCreatedDate)) {
newBlog.publishedAt = blogData.postCreatedDate;
newBlog.postCreatedDate = blogData.postCreatedDate;
} else {
newBlog.postCreatedDate = now();
}
newBlog.publishedAt = blogData.publishedAt;
newBlog.postCreatedDate = blogData.postCreatedDate;
newBlog.save();

// Retrieve the generated ID by looking up the just-created blog by slug.
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/web/NewsController.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ component extends="app.Controllers.Controller" {
try {
var blogQuery = model("Blog").findAll(
select = "title, slug, content, postDate",
where = "blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#now()#' ",
where = "blog_posts.statusId <> 1 AND blog_posts.status = 'Approved' AND blog_posts.publishedAt IS NOT NULL AND blog_posts.publishedAt <= '#toUTC(now())#' ",
order = "createdAt DESC",
cache = 10
);
Expand Down
31 changes: 31 additions & 0 deletions app/global/functions.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,37 @@
public function GetSignedInUserId(){
return structKeyExists(session, "userID") ? session.userID : 0
}

/**
* Convert a local datetime to UTC using server's timezone offset
* @localTime The datetime to convert to UTC
* @return The datetime in UTC
*/
public datetime function toUTC(required datetime localTime) {
var tzInfo = GetTimeZoneInfo();
var offsetSeconds = tzInfo.utcTotalOffset * 60;
return dateAdd("s", -offsetSeconds, arguments.localTime);
}

/**
* Safely convert a datetime string from a specific timezone to UTC
* @dateTimeStr The datetime string to convert (expected to be ISO format from JavaScript)
* @timeZone The timezone identifier (e.g., "America/New_York") - kept for compatibility but not used since JS sends UTC
* @return The datetime in UTC
*/
public datetime function toSafeUTC(required string dateTimeStr, string timeZone="") {
try {
// Since JavaScript sends ISO string (already in UTC), just parse it
if (len(trim(arguments.dateTimeStr))) {
return parseDateTime(arguments.dateTimeStr);
} else {
return toUTC(now());
}
} catch (any e) {
// Fallback: return current UTC time
return toUTC(now());
}
}
public function GetUserRoleId(){
return 3;
}
Expand Down
25 changes: 20 additions & 5 deletions app/views/layout.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@

<!--- Set og:image for key public pages --->
<cfif isHome>
<cfset ogImage = getBaseUrl() & "/images/home-og.png">
<cfset ogImage = getBaseUrl() & "/img/home-og.png">
<cfelseif isCommunity>
<cfset ogImage = getBaseUrl() & "/images/community-og.png">
<cfset ogImage = getBaseUrl() & "/img/community-og.png">
<cfelseif isGuideDocs>
<cfset ogImage = getBaseUrl() & "/images/guides-og.png">
<cfset ogImage = getBaseUrl() & "/img/guides-og.png">
<cfelseif isDocs>
<cfset ogImage = getBaseUrl() & "/images/docs-og.png">
<cfset ogImage = getBaseUrl() & "/img/docs-og.png">
<cfelseif isApi>
<cfset ogImage = getBaseUrl() & "/images/api-og.png">
<cfset ogImage = getBaseUrl() & "/img/api-og.png">
</cfif>

<cfset pageTitle = "Wheels - An open source CFML framework inspired by Ruby on Rails">
Expand Down Expand Up @@ -806,6 +806,21 @@
<script src="/js/swiper.js"></script>
<script src="/js/infinite-scroll.pkgd.min.js"></script>
<script src="/js/global.js"></script>
<script>
// Convert UTC dates to local timezone for elements with data-utc attribute
document.addEventListener("DOMContentLoaded", function() {
var utcElements = document.querySelectorAll('[data-utc]');
utcElements.forEach(function(el) {
var utcDate = el.getAttribute('data-utc');
if (utcDate) {
var date = new Date(utcDate);
if (!isNaN(date.getTime())) {
el.textContent = date.toLocaleString();
}
}
});
});
</script>
</body>
</html>
</cfif>
Loading