diff --git a/app/controllers/Controller.cfc b/app/controllers/Controller.cfc
index 709b774..8982162 100644
--- a/app/controllers/Controller.cfc
+++ b/app/controllers/Controller.cfc
@@ -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
);
diff --git a/app/controllers/web/BlogController.cfc b/app/controllers/web/BlogController.cfc
index 719d9a0..68ccb5e 100644
--- a/app/controllers/web/BlogController.cfc
+++ b/app/controllers/web/BlogController.cfc
@@ -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,
@@ -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;
@@ -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,
@@ -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;
@@ -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
@@ -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,
@@ -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;
@@ -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",
@@ -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;
@@ -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",
@@ -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;
@@ -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",
@@ -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;
@@ -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 = "";
}
@@ -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;
@@ -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.
diff --git a/app/controllers/web/NewsController.cfc b/app/controllers/web/NewsController.cfc
index c62f0f2..070cd34 100644
--- a/app/controllers/web/NewsController.cfc
+++ b/app/controllers/web/NewsController.cfc
@@ -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
);
diff --git a/app/global/functions.cfm b/app/global/functions.cfm
index 345536a..07c2957 100644
--- a/app/global/functions.cfm
+++ b/app/global/functions.cfm
@@ -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;
}
diff --git a/app/views/layout.cfm b/app/views/layout.cfm
index 3f1293b..d336720 100644
--- a/app/views/layout.cfm
+++ b/app/views/layout.cfm
@@ -17,15 +17,15 @@
-
+
-
+
-
+
-
+
-
+
@@ -806,6 +806,21 @@
+