diff --git a/civicrm-ux.php b/civicrm-ux.php
index 88f5113..d4e99cd 100644
--- a/civicrm-ux.php
+++ b/civicrm-ux.php
@@ -9,7 +9,7 @@
* Plugin Name: WP CiviCRM UX
* Plugin URI: https://github.com/agileware/wp-civicrm-ux
* Description: A better user experience for integrating WordPress and CiviCRM
- * Version: 1.19.0
+ * Version: 1.20.0
* Requires at least: 5.8
* Requires PHP: 7.4
* Requires Plugins: civicrm
diff --git a/includes/class-civicrm-ux.php b/includes/class-civicrm-ux.php
index 34bb4fd..ca5c2d7 100755
--- a/includes/class-civicrm-ux.php
+++ b/includes/class-civicrm-ux.php
@@ -160,6 +160,7 @@ private function load_dependencies() {
*/
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-civicrm-ux-helper.php';
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-civicrm-ux-option-store.php';
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/utils/class-civicrm-ux-validators.php';
// All module mangers and instances class
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/interface/interface-civicrm-ux-managed-instance.php';
diff --git a/includes/utils/class-civicrm-ux-validators.php b/includes/utils/class-civicrm-ux-validators.php
new file mode 100644
index 0000000..5824621
--- /dev/null
+++ b/includes/utils/class-civicrm-ux-validators.php
@@ -0,0 +1,57 @@
+ * {
z-index: 1;
}
-
-
-.tippy-box[data-theme~='fcvic'] {
- background-color: #f0e8ce;
- border-color: #f0e8ce;
- color: black;
-}
-
-.tippy-box[data-theme~='fcvic'][data-placement^='top'] > .tippy-arrow::before {
- border-top-color: #f0e8ce;
-}
-.tippy-box[data-theme~='fcvic'][data-placement^='bottom'] > .tippy-arrow::before {
- border-bottom-color: #f0e8ce;
-}
-.tippy-box[data-theme~='fcvic'][data-placement^='left'] > .tippy-arrow::before {
- border-left-color: #f0e8ce;
-}
-.tippy-box[data-theme~='fcvic'][data-placement^='right'] > .tippy-arrow::before {
- border-right-color: #f0e8ce;
-}
-
-
-.tippy-box[data-theme~='fcvic'] img {
- max-width: 90%;
- max-height: 50px;
- height: auto;
- margin: 10px 10px 0px 10px;
- display: block;
-}
-
-
.event-holder {
overflow: hidden;
padding: 3px 3px 3px 3px;
@@ -289,6 +245,7 @@ div.civicrm-event-listing-item {
.event-time {
margin: 5px 0px 0px 10px;
display: inline-flex;
+ align-items: center;
}
.event-time-text {
@@ -297,6 +254,10 @@ div.civicrm-event-listing-item {
margin-left: 5px;
}
+.tippy-box .event-time-text {
+ color: #ffffff;
+}
+
.event-location {
margin: 0px 0px 5px 10px;
}
diff --git a/public/js/event-fullcalendar.js b/public/js/event-fullcalendar.js
index 6289351..04216f0 100644
--- a/public/js/event-fullcalendar.js
+++ b/public/js/event-fullcalendar.js
@@ -1,320 +1,326 @@
+(function() {
const domevent = function (eventName, detail) {
if (typeof Window.CustomEvent === "function") {
- return new CustomEvent(eventName, {
- bubbles: true,
- cancelable: true,
- detail,
- });
+ return new CustomEvent(eventName, {
+ bubbles: true,
+ cancelable: true,
+ detail,
+ });
} else {
- const event = document.createEvent("CustomEvent");
- event.initCustomEvent(eventName, true, true, detail);
- return event;
+ const event = document.createEvent("CustomEvent");
+ event.initCustomEvent(eventName, true, true, detail);
+ return event;
}
- };
-
- /*
- Convert ISO8601 date object to AM/PM time format
- E.g. 2021-02-02T09:30:00+0000 => 9:30AM
- */
- function formatAMPM(date) {
- return date.toLocaleTimeString([], { timeStyle: "short" });
- }
-
- function hidePopup() {
+};
+
+/*
+ Convert ISO8601 date object to AM/PM time format
+ E.g. 2021-02-02T09:30:00+0000 => 9:30AM
+*/
+const formatAMPM = function (date) {
+ return date.toLocaleTimeString([], {timeStyle: "short"});
+}
+
+window.hideEventsCalendarPopup = function() {
document.querySelector(".civicrm-ux-event-popup").style.display = "none";
document.getElementById("civicrm-event-fullcalendar").style.display = "flex";
- }
+}
- function sameDay(d1, d2) {
+const sameDay = function (d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
- d1.getMonth() === d2.getMonth() &&
- d1.getDate() === d2.getDate();
- }
-
- // Used to convert an index to a day-of-the-week/month
- // E.g. days[Date.getDay()] => days[1] => Monday
- const days = [
- "Sunday",
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- ];
- const months = [
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December",
- ];
-
- /*
- Convert ISO8601 date object to human readable format
- E.g. 2021-02-02T09:30:00+0000 => Tuesday, 2 February 2021
- */
- function formatDay(date) {
- return (
- days[date.getDay()] +
- ", " +
- date.getDate() +
- " " +
- months[date.getMonth()] +
- " " +
- date.getFullYear()
- );
- }
-
- let events_loaded = false;
- let prev_rendered_date;
- let prev_rendered_date_visible = false;
-
- document.addEventListener("DOMContentLoaded", function () {
- const calendarEl = document.getElementById("civicrm-event-fullcalendar");
-
- // ColoUr scheme for different event type labels
- const colors = wp_site_obj.colors;
- const event_types = wp_site_obj.types.split(',');
+ d1.getMonth() === d2.getMonth() &&
+ d1.getDate() === d2.getDate();
+}
- /*
- This object defines the custom parameters for FullCalendar's library incl. buttons, logic, views
- */
- let calendarParams = {
- initialView: "dayGridMonth",
- nextDayThreshold: '09:00:00',
- events: function (info, successCallback, failureCallback) {
- if (!events_loaded) {
- // Make AJAX request (themes > functions.php) to get all events from 1 year ago until now
- jQuery.ajax({
+let events_loaded = false;
+let prev_rendered_date;
+let prev_rendered_date_visible = false;
+
+// Colour scheme for different event type labels
+const colors = uxFullcalendar.colors || {};
+const event_types = uxFullcalendar.filterTypes || [];
+
+const events = function (info, successCallback, failureCallback) {
+ if (!events_loaded) {
+ // Make AJAX request (themes > functions.php) to get all events from 1 year ago until now
+ wp.apiRequest({
method: "GET",
dataType: "json",
- url: wp_site_obj.ajax_url + 'civicrm_ux/get_events_all',
+ url: uxFullcalendar.ajax_url + 'civicrm_ux/get_events_all',
data: {
- type: wp_site_obj.types,
- upload: wp_site_obj.upload,
- colors: colors,
- start_date: wp_site_obj.start,
- image_id_field: wp_site_obj.image_id_field,
- image_src_field: wp_site_obj.image_src_field,
- force_login: wp_site_obj.force_login,
- redirect_after_login: wp_site_obj.redirect_after_login,
- extra_fields: wp_site_obj.extra_fields
+ type: uxFullcalendar.types,
+ upload: uxFullcalendar.upload,
+ colors: colors,
+ start_date: uxFullcalendar.start,
+ image_id_field: uxFullcalendar.image_id_field,
+ image_src_field: uxFullcalendar.image_src_field,
+ force_login: uxFullcalendar.force_login,
+ redirect_after_login: uxFullcalendar.redirect_after_login,
+ extra_fields: uxFullcalendar.extra_fields
},
// Store events in client's browser after success to prevent further AJAX requests
success: function (response) {
- all_the_events = response.result;
- events_loaded = true;
- successCallback(response.result);
+ all_the_events = response.result;
+ events_loaded = true;
+ successCallback(response.result);
},
- });
- } else {
- successCallback(all_the_events);
+ });
+ } else {
+ successCallback(all_the_events);
+ }
+};
+
+const eventContent = function (arg) {
+ const selector_val = document.querySelector("#event-selector")?.value ?? 'all';
+
+ const {
+ extendedProps: { html_entry, event_type }
+ } = arg.event;
+
+ if (
+ selector_val == "all" ||
+ selector_val == arg.event.extendedProps.event_type
+ ) {
+ const eventTemplate = document.createElement("template");
+ eventTemplate.innerHTML = html_entry;
+
+ const eventHolder = eventTemplate.content.firstElementChild;
+ eventHolder.classList.add("event-holder");
+
+ if (colors.hasOwnProperty(event_type))
+ eventHolder.style.backgroundColor = colors[event_type];
+
+ return { domNodes: eventTemplate.content.childNodes };
+ }
+
+ return {};
+}
+
+const eventDidMount = function (info) {
+ let selector_val = document.querySelector("#event-selector")?.value ?? 'all';
+
+ const event_start = info.event.start;
+ const event_end = info.event.end;
+
+ const {
+ extendedProps: {
+ event_type,
+ timeZone,
+ 'file.uri': fileUri = undefined,
+ image_url,
+ country,
+ html_render = undefined
}
- },
- eventTimeFormat: {
- hour: "numeric",
- minute: "2-digit",
- omitZeroMinute: false,
- meridiem: "short",
- },
- customButtons: {
- sortByType: {
- text: "Filter by Event Type",
- },
- },
- headerToolbar: {
- left: "dayGridMonth,listMonth sortByType",
- center: "",
- end: "prevYear,prev title next,nextYear",
- },
- buttonText: {
- dayGridMonth: "Calendar View",
- listMonth: "List View",
- prevYear: "« Previous year",
- prev: "‹ Previous month",
- nextYear: "Next year »",
- next: "Next month ›",
- },
- buttonIcons: {
- prevYear: "chevrons-left",
- prev: "chevron-left",
- nextYear: "chevrons-right",
- next: "chevron-right",
- },
- firstDay: 1,
- // Event render hooks (eventContent, eventDidMount, eventClick) define the custom logic for FCVIC's fullcalendar
-
- // Content injection to change the colour of event labels (by type)
- eventContent: function (arg) {
- let selector_val = document.querySelector("#event-selector").value;
-
- if (
- selector_val == "all" ||
- selector_val == arg.event.extendedProps.event_type
- ) {
- let eventHolder = document.createElement("div");
- eventHolder.classList.add("event-holder");
- eventHolder.style.backgroundColor =
- Object.keys(colors).length > 0 ? '#' + colors[arg.event.extendedProps.event_type] : '#333333';
- eventHolder.innerHTML =
- '
";
-
- let arrayOfDomNodes = [eventHolder];
- return { domNodes: arrayOfDomNodes };
- } else return {};
- },
-
- // Executed for each event when they are added to the DOM
- eventDidMount: function (info) {
- let selector_val = document.querySelector("#event-selector").value;
-
- const event_start = new Date(info.event.start);
- const event_end = new Date(info.event.end);
+ } = info.event;
- if (info.view.type == "listMonth") {
- if (prev_rendered_date != event_start.getDate()) {
+ if (info.view.type == "listMonth") {
+ if (prev_rendered_date != event_start.getDate()) {
prev_rendered_date = event_start.getDate;
prev_rendered_date_visible = false;
- }
- const onthisdate =
+ }
+ const onthisdate =
event_start.getFullYear() +
"-" +
- (event_start.getMonth() + 1 < 10
- ? "0" + (event_start.getMonth() + 1)
- : event_start.getMonth() + 1) +
+ (event_start.getMonth() + 1 + '').padStart(2, '0')
"-" +
- (event_start.getDate() < 10
- ? "0" + event_start.getDate()
- : event_start.getDate());
-
- let date_row = document.querySelectorAll(
- "[data-date='" + onthisdate + "']"
- )[0];
-
- if (!prev_rendered_date_visible) {
- date_row.style.display = "none";
- } else {
- date_row.style.display = "table-row";
- }
-
- if (info.event.extendedProps.event_type == selector_val) {
- prev_rendered_date_visible = true;
- date_row.style.display = "table-row";
- }
-
- if (selector_val == "all") {
- date_row.style.display = "table-row";
- }
-
- prev_rendered_date = event_start.getDate();
-
- if (
- info.event.extendedProps.event_type != selector_val &&
- !prev_rendered_date_visible
- ) {
- prev_rendered_date_visible = false;
- }
+ (event_start.getDate() + '').padStart(2, '0');
+
+ let date_row = document.querySelector("[data-date='" + onthisdate + "']");
+
+ if(date_row) {
+ if (!prev_rendered_date_visible) {
+ date_row.style.display = "none";
+ } else {
+ date_row.style.display = "table-row";
+ }
+
+ if (event_type == selector_val) {
+ prev_rendered_date_visible = true;
+ date_row.style.display = "table-row";
+ }
+
+ if (selector_val == "all") {
+ date_row.style.display = "table-row";
+ }
}
-
+
+ prev_rendered_date = event_start.getDate();
+
if (
- selector_val == "all" ||
- selector_val == info.event.extendedProps.event_type
+ event_type != selector_val &&
+ !prev_rendered_date_visible
) {
- let event_img = info.event.extendedProps["file.uri"] ? info.event.extendedProps["image_url"] : "";
-
-
- if (info.view.type == "listMonth") {
- const template = info.event.extendedProps.html_render;
- info.el.innerHTML = template;
- } else {
- const event_location = info.event.extendedProps.country;
-
- const template = `
- ${info.event.title}
- ${
- info.event.extendedProps["file.uri"]
- ? ' '
- : ""
- }
- ${
- formatAMPM(event_start) + " to " + formatAMPM(event_end)
- }
- ${
- event_location
- ? '' + event_location + '
'
- : ""
- }
- `;
-
+ prev_rendered_date_visible = false;
+ }
+ }
+
+ if (
+ selector_val == "all" ||
+ selector_val == event_type
+ ) {
+ let event_img = (fileUri ?? false) ? image_url : "";
+
+ if (info.view.type == "listMonth") {
+ if(html_render) {
+ info.el.innerHTML = html_render;
+
+ for (const el of Array.from(info.el.children).filter((el) => !(el instanceof HTMLTableCellElement))) {
+ const wrapEl = document.createElement('TD');
+ el.replaceWith(wrapEl);
+ wrapEl.append(el);
+ }
+
+ const colSpan = Math.max(1, 4 - info.el.children.length);
+ info.el.lastElementChild.setAttribute('colspan', colSpan);
+ }
+ } else {
+ const event_location = country;
+
+ const fmt_AMPM = new Intl.DateTimeFormat([], { timeStyle: 'short', timeZone })
+
+ const eventElements = {
+ eventImg: '',
+ eventLocation: '',
+ eventTimeText: `
+ ${fmt_AMPM.format(event_start)}
+ to ${fmt_AMPM.format(event_end)}
+ `,
+ }
+
+ if(info.event.extendedProps["file.uri"]) {
+ eventElements.eventImg = ` `;
+ }
+
+ if(event_location) {
+ eventElements.eventLocation = `${event_location}
`
+ }
+
+ info.view.calendar.el.dispatchEvent(domevent('fullcalendar:buildTippy', eventElements));
+
+ const template = document.createElement('template');
+
+ template.innerHTML =
+ `${info.event.title}
+ ${eventElements.eventImg}
+ ${eventElements.eventTimeText}
+ ${eventElements.eventLocation}`;
+
+ if(colors.hasOwnProperty(event_type)) {
+ template.content.querySelector('.event-name').style.backgroundColor = colors[event_type];
+ }
+
+ info.view.calendar.el.dispatchEvent(domevent('fullcalendar:alterTippy', template.content));
+
let tooltip = new tippy(info.el, {
- interactive: true,
- delay: 300,
- maxWidth: 400,
- allowHTML: true,
- placement: "top",
- content(reference) {
- return template;
- },
+ interactive: true,
+ delay: 300,
+ maxWidth: 400,
+ allowHTML: true,
+ placement: "top",
+ content: template.content,
});
- }
- } else {
- info.el.innerHTML = "";
}
- },
-
- eventClick: function (eventClickInfo) {
- let jsEvent = eventClickInfo.jsEvent;
- jsEvent.preventDefault();
-
- let event_container = document.querySelector(
- ".civicrm-ux-event-popup-container"
- );
- let popup = document.querySelector(".civicrm-ux-event-popup");
- let popup_container = document.getElementById("civicrm-ux-event-popup-content");
- event_container.style.display = "none";
- calendarEl.style.display = "none";
- popup.style.display = "block";
- popup_container.innerHTML = eventClickInfo.event.extendedProps.html_render;
-
- event_container.style.display = "block";
- },
+ } else {
+ info.el.innerHTML = "";
+ }
+};
+
+const eventClick = function ({el, event, jsEvent, view}) {
+ jsEvent.preventDefault();
+
+ let event_container = document.querySelector(
+ ".civicrm-ux-event-popup-container"
+ );
+ let popup = document.querySelector(".civicrm-ux-event-popup");
+ let popup_container = document.getElementById("civicrm-ux-event-popup-content");
+ event_container.style.display = "none";
+ view.calendar.el.style.display = "none";
+ popup.style.display = "block";
+ popup_container.innerHTML = event.extendedProps.html_render;
+
+ event_container.style.display = "block";
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+ const calendarEl = document.getElementById("civicrm-event-fullcalendar");
+
+ /*
+ This object defines the custom parameters for FullCalendar's library incl. buttons, logic, views
+ */
+ let calendarParams = {
+ initialView: "dayGridMonth",
+ nextDayThreshold: '09:00:00',
+ events,
+ eventTimeFormat: {
+ hour: "numeric",
+ minute: "2-digit",
+ omitZeroMinute: false,
+ meridiem: "short",
+ },
+ customButtons: {
+ sortByType: {
+ text: "Filter by Event Type",
+ },
+ },
+ headerToolbar: {
+ left: "dayGridMonth,listMonth sortByType",
+ center: "",
+ end: "prevYear,prev title next,nextYear",
+ },
+ buttonText: {
+ dayGridMonth: "Calendar View",
+ listMonth: "List View",
+ prevYear: "« Previous year",
+ prev: "‹ Previous month",
+ nextYear: "Next year »",
+ next: "Next month ›",
+ },
+ buttonIcons: {
+ prevYear: "chevrons-left",
+ prev: "chevron-left",
+ nextYear: "chevrons-right",
+ next: "chevron-right",
+ },
+ firstDay: 1,
+ // Event render hooks (eventContent, eventDidMount, eventClick) define the custom logic for FCVIC's fullcalendar
+
+ // Content injection to change the colour of event labels (by type)
+ eventContent,
+
+ // Executed for each event when they are added to the DOM
+ eventDidMount,
+
+ // Callback to be run when the calendar entry is clicked
+ eventClick,
+
+ ...(window.uxFullcalendar.calendar_params || {})
};
-
+
calendarEl.dispatchEvent(
- domevent("fullcalendar:buildparams", calendarParams)
+ domevent("fullcalendar:buildparams", calendarParams)
);
-
+
const calendar = new FullCalendar.Calendar(calendarEl, calendarParams);
-
+
calendarEl.dispatchEvent(domevent("fullcalendar:prerender"));
calendar.render();
const sortByTypeBtn = document.querySelector(".fc-sortByType-button");
+
+ if (!sortByTypeBtn)
+ return;
+
const sortByTypeSelect = document.createElement("select");
sortByTypeSelect.classList.add("fc-button");
sortByTypeSelect.classList.add("fc-button-primary");
sortByTypeSelect.innerHTML = 'Filter by Event Type ';
- for (const i in event_types) {
- sortByTypeSelect.innerHTML += '' + event_types[i] + ' ';
+ for (const type of event_types) {
+ sortByTypeSelect.innerHTML += '' + type + ' ';
}
sortByTypeSelect.setAttribute("id", "event-selector");
sortByTypeBtn.style.display = "none";
sortByTypeBtn.after(sortByTypeSelect);
-
+
sortByTypeSelect.addEventListener("change", () => calendar.refetchEvents());
- });
-
\ No newline at end of file
+});
+})();
\ No newline at end of file
diff --git a/rest/json-all-events.php b/rest/json-all-events.php
index dff5900..da4a7ab 100644
--- a/rest/json-all-events.php
+++ b/rest/json-all-events.php
@@ -2,6 +2,8 @@
use Civi\Api4\Event;
+use Civicrm_Ux_Shortcode_Event_FullCalendar as Shortcode;
+
class Civicrm_Ux_REST_JSON_All_Events extends Abstract_Civicrm_Ux_REST {
/**
@@ -44,192 +46,141 @@ public function rest_api_callback( $data ) {
protected function get_events_all() {
$types = array();
$start_date = preg_replace("([^0-9-])", "", $_REQUEST['start_date']);
- $force_login = rest_sanitize_boolean($_REQUEST['force_login']);
+ $force_login = rest_sanitize_boolean($_REQUEST['force_login'] ?? Shortcode::getDefaultForceLogin());
$redirect_after_login = esc_url($_REQUEST['redirect_after_login']);
- $extra_fields = $_REQUEST['extra_fields'] != '' ? explode(',', filter_var($_REQUEST['extra_fields'], FILTER_SANITIZE_STRING)) : array();
- $colors = $_REQUEST['colors'] ?? [];
- filter_var($_REQUEST['upload'], FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED);
- $upload = $_REQUEST['upload'];
+ $extra_fields = !empty($_REQUEST['extra_fields']) ? explode(',', filter_var($_REQUEST['extra_fields'], FILTER_SANITIZE_STRING)) : [];
+ if(!empty($_REQUEST['colors']) && !is_array($_REQUEST['colors'])) {
+ $_REQUEST['colors'] = explode(',', $_REQUEST['colors']);
+ }
+ $colors = array_map ( [ 'Civicrm_Ux_Validators', 'validateCssColor' ], $_REQUEST['colors'] ?? [] );
+ $upload = filter_var($_REQUEST['upload'], FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED);
+
+ $colors = array_filter($colors);
+ $colors[ 'default' ] ??= Shortcode::getDefaultColor();
if (isset($_REQUEST['type'])) {
$types_tmp = explode(",", $_REQUEST['type']);
for ($i = 0; $i < count($types_tmp); $i++) {
array_push($types, preg_replace('/[^a-zA-Z0-9 ]/', '', $types_tmp[$i]));
}
-
- }
-
- foreach ($colors as $k => $v) {
- $colors[$k] = sanitize_hex_color_no_hash($v);
- if (!ctype_alnum($k)) {
- unset($colors[$k]);
- }
}
+ foreach($types as $idx => $type) {
+ $colors[$type] ??= $colors[$idx] ?? Shortcode::getDefaultColor();
+ }
$res = array('success' => true);
try {
$events = array();
- if ($_REQUEST['image_id_field'] != "") {
- $image_id_field = $_REQUEST['image_id_field'];
- $image_src_field = $_REQUEST['image_src_field'];
-
- $events = Event::get(FALSE)
- ->addSelect('id', 'title', 'summary', 'description', 'event_type_id:label', 'start_date', 'end_date', 'file.id', $image_src_field, 'address.street_address', 'address.street_number', 'address.street_number_suffix', 'address.street_name', 'address.street_type', 'address.country_id:label', 'is_online_registration', ...$extra_fields)
- ->addJoin('File AS file', 'LEFT', ['file.id', '=', $image_id_field])
- ->addJoin('LocBlock AS loc_block', 'LEFT', ['loc_block_id', '=', 'loc_block_id.id'])
- ->addJoin('Address AS address', 'LEFT', ['loc_block.address_id', '=', 'address.id'])
- ->addWhere('event_type_id:label', 'IN', $types)
- ->addWhere('start_date', '>=', $start_date)
- ->addWhere('is_public', '=', TRUE)
- ->addWhere('is_active', '=', TRUE)
- ->addOrderBy('start_date', 'ASC')
- ->execute();
-
- $res['result'] = array();
-
- $tz = wp_timezone();
-
- foreach ($events as $event) {
- if ( !empty($redirect_after_login) ) {
- // If we have specified a custom redirection after login, apply that
- $params = array(
- 'id' => $event['id'],
- 'reset' => 1
- );
- $url = add_query_arg($params, $redirect_after_login);
- } else {
- // Otherwise redirect to the standard civicrm event registration page
- $url = CRM_Utils_System::url('civicrm/event/register', ['id' => $event['id'], 'reset' => 1]);
- }
-
- if (!is_user_logged_in() and $force_login) {
- $url = get_site_url() . '/wp-login.php?redirect_to=' . urlencode($url);
- }
-
- if (str_ends_with($upload, '/civicrm/custom')) {
- $fileHash = CRM_Core_BAO_File::generateFileHash($event['id'], $event['file.id']);
- $image_url = CRM_Utils_System::url('civicrm/file/imagefile',"reset=1&id={$event['file.id']}&eid={$event['id']}&fcs={$fileHash}");
- } else {
- $image_url = $upload . '/' . $event[$image_src_field];
- }
-
- $event_obj = array(
- 'id' => $event['id'],
- 'title' => $event['title'],
- 'start' => (new DateTimeImmutable($event['start_date'], $tz))->setTimezone($tz)->format(DateTime::ISO8601),
- 'end' => (new DateTimeImmutable($event['end_date'], $tz))->setTimezone($tz)->format(DateTime::ISO8601),
- 'display' => 'auto',
- 'startStr' => $event['start_date'],
- 'endStr' => $event['end_date'],
- 'url' => $url,
- 'extendedProps' => array(
- 'html_render' => $this->generate_event_html($event, $upload, $colors, $image_src_field, $url),
- 'summary' => $event['summary'],
- 'description' => $event['description'],
- 'event_type' => $event['event_type_id:label'],
- 'file.uri' => $event[$image_src_field],
- 'image_url' => $image_url,
- 'street_address' => $event['address.street_address'],
- 'street_number' => $event['address.street_number'],
- 'street_number_suffix' => $event['address.street_number_suffix'],
- 'street_name' => $event['address.street_name'],
- 'street_type' => $event['address.street_type'],
- 'country' => $event['address.country_id:label'],
- 'is_online_registration' => $event['is_online_registration'],
- 'extra_fields' => array()
- )
- );
-
- for ($i = 0; $i < count($extra_fields); $i++) {
- $event_obj['extra_fields'][$extra_fields[$i]] = $event[$extra_fields[$i]];
- }
-
- $event_obj = apply_filters( 'wp_civi_ux_event_inject_content', $event_obj );
-
- array_push($res['result'], $event_obj);
- }
- } else {
-
- $image_src_field = $_REQUEST['image_src_field'];
-
- $events = Event::get(FALSE)
- ->addSelect('id', 'title', 'summary', 'description', 'event_type_id:label', 'start_date', 'end_date', 'address.street_address', 'address.street_number', 'address.street_number_suffix', 'address.street_name', 'address.street_type', 'address.country_id:label', 'is_online_registration', ...$extra_fields)
- ->addJoin('LocBlock AS loc_block', 'LEFT', ['loc_block_id', '=', 'loc_block_id.id'])
- ->addJoin('Address AS address', 'LEFT', ['loc_block.address_id', '=', 'address.id'])
- ->addWhere('event_type_id:label', 'IN', $types)
- ->addWhere('start_date', '>=', $start_date)
- ->addWhere('is_public', '=', TRUE)
- ->addWhere('is_active', '=', TRUE)
- ->addOrderBy('start_date', 'ASC')
- ->execute();
-
- $res['result'] = array();
-
-
- foreach ($events as $event) {
- if ( !empty($redirect_after_login) ) {
- // If we have specified a custom redirection after login, apply that
- $params = array(
- 'id' => $event['id'],
- 'reset' => 1
- );
- $url = add_query_arg($params, $redirect_after_login);
- } else {
- // Otherwise redirect to the standard civicrm event registration page
- $url = CRM_Utils_System::url('civicrm/event/register', ['id' => $event['id'], 'reset' => 1]);
- }
-
- if (!is_user_logged_in() and $force_login) {
- $url = get_site_url() . '/wp-login.php?redirect_to=' . urlencode($url);
- }
-
- $event_obj = array(
- 'id' => $event['id'],
- 'title' => $event['title'],
- 'start' => date(DATE_ISO8601, strtotime($event['start_date'])),
- 'end' => date(DATE_ISO8601, strtotime($event['end_date'])),
- 'display' => 'auto',
- 'startStr' => $event['start_date'],
- 'endStr' => $event['end_date'],
- 'url' => $url,
- 'extendedProps' => array(
- 'html_render' => $this->generate_event_html($event, $upload, $colors, $image_src_field, $url),
- 'summary' => $event['summary'],
- 'description' => $event['description'],
- 'event_type' => $event['event_type_id:label'],
- 'street_address' => $event['address.street_address'],
- 'street_number' => $event['address.street_number'],
- 'street_number_suffix' => $event['address.street_number_suffix'],
- 'street_name' => $event['address.street_name'],
- 'street_type' => $event['address.street_type'],
- 'country' => $event['address.country_id:label'],
- 'is_online_registration' => $event['is_online_registration'],
- 'extra_fields' => array()
- )
- );
-
- for ($i = 0; $i < count($extra_fields); $i++) {
- $event_obj['extra_fields'][$extra_fields[$i]] = $event[$extra_fields[$i]];
- }
-
- $event_obj = apply_filters( 'wp_civi_ux_event_inject_content', $event_obj );
-
- array_push($res['result'], $event_obj);
- }
+ $eventQuery = Event::get(FALSE)
+ ->addSelect('id', 'title', 'summary', 'description', 'event_type_id:label', 'start_date', 'end_date', 'address.street_address', 'address.street_number', 'address.street_number_suffix', 'address.street_name', 'address.street_type', 'address.country_id:label', 'is_online_registration', ...$extra_fields)
+ ->addJoin('LocBlock AS loc_block', 'LEFT', ['loc_block_id', '=', 'loc_block_id.id'])
+ ->addJoin('Address AS address', 'LEFT', ['loc_block.address_id', '=', 'address.id'])
+ ->addWhere('start_date', '>=', $start_date)
+ ->addWhere('is_public', '=', TRUE)
+ ->addWhere('is_active', '=', TRUE)
+ ->addOrderBy('start_date', 'ASC');
+
+ if(!empty($types)) {
+ $eventQuery->addWhere('event_type_id:name', 'IN', $types);
+ }
+
+ $image_src_field = Civicrm_Ux_Validators::validateAPIFieldName($_REQUEST['image_src_field']);
+
+ if (!empty($_REQUEST['image_id_field'])) {
+ $image_id_field = Civicrm_Ux_Validators::validateAPIFieldName($_REQUEST['image_id_field']);
+
+ $eventQuery
+ ->addJoin('File AS file', 'LEFT', ['file.id', '=', $image_id_field])
+ ->addSelect('file.id', $image_src_field);
+ }
+
+ $events = $eventQuery->execute();
+
+ $res['result'] = array();
+
+ $tz = wp_timezone();
+
+ foreach ($events as $event) {
+ if ( !empty($redirect_after_login) ) {
+ // If we have specified a custom redirection after login, apply that
+ $params = [ 'id' => $event['id'], 'reset' => 1, ];
+
+ $url = add_query_arg($params, $redirect_after_login);
+
+ } else {
+ // Otherwise redirect to the standard civicrm event registration page
+ $url = CRM_Utils_System::url('civicrm/event/register', ['id' => $event['id'], 'reset' => 1]);
+ }
+
+ if (!is_user_logged_in() and $force_login) {
+ $url = get_site_url() . '/wp-login.php?redirect_to=' . urlencode($url);
+ }
+
+ $event += [
+ 'url' => $url,
+ 'start' => new DateTimeImmutable($event['start_date'], $tz),
+ 'end' => new DateTimeImmutable($event['end_date'], $tz),
+ ];
+
+ $event_obj = array(
+ 'id' => $event['id'],
+ 'title' => $event['title'],
+ 'start' => $event['start']->format( DateTime::ATOM ),
+ 'end' => $event[ 'end' ]->format( DateTime::ATOM ),
+
+ 'display' => 'auto',
+ 'startStr' => $event['start_date'],
+ 'endStr' => $event['end_date'],
+ 'url' => $url,
+ 'extendedProps' => array(
+ 'html_render' => $this->generate_event_html($event, $upload, $colors, $image_src_field, $url),
+ 'html_entry' => $this->get_output_template(['event' => $event]),
+ 'timeZone' => $tz->getName(),
+ 'summary' => $event['summary'],
+ 'description' => $event['description'],
+ 'event_type' => $event['event_type_id:label'],
+ 'street_address' => $event['address.street_address'],
+ 'street_number' => $event['address.street_number'],
+ 'street_number_suffix' => $event['address.street_number_suffix'],
+ 'street_name' => $event['address.street_name'],
+ 'street_type' => $event['address.street_type'],
+ 'country' => $event['address.country_id:label'],
+ 'is_online_registration' => $event['is_online_registration'],
+ 'extra_fields' => []
+ )
+ );
+
+ if(!empty($image_src_field) && !empty($event['file.id'])) {
+ if (str_ends_with($upload, '/civicrm/custom')) {
+ $fileHash = CRM_Core_BAO_File::generateFileHash($event['id'], $event['file.id']);
+ $image_url = CRM_Utils_System::url('civicrm/file/imagefile', "reset=1&id={$event['file.id']}&eid={$event['id']}&fcs={$fileHash}");
+ } else {
+ $image_url = $upload . '/' . $event[$image_src_field];
+ }
+
+ $event_obj['extendedProps']['file.uri'] = $event[$image_src_field];
+ $event_obj['extendedProps']['image_url'] = $image_url;
+ }
+
+ foreach ($extra_fields as $field) {
+ $event_obj['extra_fields'][$field] = $event[$field];
+ }
+
+ $event_obj = apply_filters( 'wp_civi_ux_event_inject_content', $event_obj );
+
+ $res['result'][] = $event_obj;
}
- } catch (CiviCRM_API4_Exception $e) {
- $res['err'] = $e;
+ } catch (CRM_Core_Exception $e) {
+ http_response_code(500);
+ $res['err'] = $e->getMessage();
}
echo json_encode($res);
}
- /**
- * TODO: WPCIVIUX-149 Convert to a template file
- */
protected function generate_event_html($event, $upload, $colors, $image_src_field, $url) {
$date_start = date_create($event['start_date']);
$date_end = date_create($event['end_date']);
@@ -249,40 +200,27 @@ protected function generate_event_html($event, $upload, $colors, $image_src_fiel
$event_location = $event['address.street_address'] ? $event['address.street_address'] . ', ' : '';
$event_location .= $event['address.country_id:label'] ? $event['address.country_id:label'] : '';
- if (str_ends_with($upload, '/civicrm/custom') && $event['file.id']) {
+ if (str_ends_with($upload, '/civicrm/custom') && !empty($event['file.id'])) {
$fileHash = CRM_Core_BAO_File::generateFileHash($event['id'], $event['file.id']);
$image_url = CRM_Utils_System::url('civicrm/file/imagefile',"reset=1&id={$event['file.id']}&eid={$event['id']}&fcs={$fileHash}", true, "", false, true, false);
+ } elseif ( ! empty($event[$image_src_field]) ) {
+ $image_url = $upload . '/' . $event[$image_src_field];
} else {
- $image_url = $upload . '/' . $event[$image_src_field];
- }
-
-
- $template = '';
- $template .= $event[$image_src_field] ? '
' : '';
-
- $template .= '
';
- $template .= '
';
- $template .= '
' . $event['event_type_id:label'] . '
-
' . $event['title'] . '
-
' . $event_time . '
';
-
- if ( !empty($event_location) ) {
- $template .= '
' . $event_location . '
';
- }
- $template .= '
';
-
- $template .= '
';
- $template .= $event['is_online_registration'] ? 'Click here to register ' : '';
- $template .= '
';
- $template .= '
';
-
- $template .= '
';
- $template .= $event['description'] ? $event['description'] . '
' : 'No event description provided
-
- ';
-
- return $template;
- }
+ $image_url = '';
+ }
+
+ $template_args = compact(
+ 'event',
+ 'image_src_field',
+ 'image_url',
+ 'colors',
+ 'event_time',
+ 'event_location',
+ 'url'
+ );
+
+ return civicrm_ux_get_template_part('event-fullcalendar', 'event', $template_args);
+ }
protected static function formatDay( $date ) {
return $date->format( 'l' ) . ', ' . $date->format( 'j' ) . ' ' . $date->format( 'F' ) . ' ' . $date->format( 'Y' );
@@ -296,4 +234,7 @@ protected static function sameDay( $d1, $d2 ) {
return ( $d1->format( 'j' ) == $d2->format( 'j' ) ) && ( $d1->format( 'f' ) == $d2->format( 'f' ) ) && ( $d1->format( 'Y' ) == $d2->format( 'Y' ) );
}
+ protected function get_output_template($args) {
+ return civicrm_ux_get_template_part('event-fullcalendar', 'event-entry', $args);
+ }
}
diff --git a/shortcodes/event/event-fullcalendar.php b/shortcodes/event/event-fullcalendar.php
index 4847f13..c3b8a48 100644
--- a/shortcodes/event/event-fullcalendar.php
+++ b/shortcodes/event/event-fullcalendar.php
@@ -62,26 +62,15 @@ public function shortcode_callback( $atts = [], $content = null, $tag = '' ) {
}
- $types = array();
- $types_ = Event::getFields(FALSE)
- ->setLoadOptions([
- 'name',
- 'label'
- ])
- ->addWhere('name', '=', 'event_type_id')
- ->addSelect('options')
- ->execute()[0]['options'];
- foreach ($types_ as $type) {
- array_push($types, $type['name']);
- }
-
// Shortcode parameters defaults
$wporg_atts = shortcode_atts(
array(
- 'types' => join(',', $types),
+ 'types' => NULL,
+ 'colors' => NULL,
'upload' => wp_upload_dir()['baseurl'] . '/civicrm/custom',
- 'force_login' => true,
+ 'force_login' => FALSE,
'start' => date('Y-m-d', strtotime('-1 year')),
+ 'image_id_field' => NULL,
'image_src_field' => 'file.uri',
'extra_fields' => join(",", $extra_fields_arr)
), $atts, $tag
@@ -89,51 +78,67 @@ public function shortcode_callback( $atts = [], $content = null, $tag = '' ) {
$redirect_after_login = isset($atts['redirect_after_login']) ? $atts['redirect_after_login'] : '';
- $colors = array();
-
- if (isset($atts['colors'])) {
- $types_arr = explode(',', $wporg_atts['types']);
- for ($i = 0; $i < count($colors_arr); $i++) {
- if ($i >= count($types_arr)) {
- break;
- }
- $colors[$types_arr[$i]] = $colors_arr[$i];
- }
- } else {
- $types_arr = explode(',', $wporg_atts['types']);
- for ($i = 0; $i < count($types_arr); $i++) {
- $colors[$types_arr[$i]] = '333333';
- }
- }
+ $colors = [ 'default' => static::getDefaultColor() ];
+ if(!empty($wporg_atts['types']) && !empty($wporg_atts['colors'])) {
+ $types_arr = explode(',', $wporg_atts['types']);
+ $limit = min(count($types_arr), count($colors_arr));
+ for ($i = 0; $i < $limit; $i++) {
+ $colors[$types_arr[$i]] = Civicrm_Ux_Validators::validateCssColor($colors_arr[$i]);
+ }
+ }
+ wp_enqueue_style( 'font-awesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css', [] );
+ wp_enqueue_style( 'fullcalendar-styles', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.11.5/main.min.css', [] );
wp_enqueue_style( 'ux-fullcalendar-styles', WP_CIVICRM_UX_PLUGIN_URL . WP_CIVICRM_UX_PLUGIN_NAME . '/public/css/event-fullcalendar.css', [] );
- wp_enqueue_style( 'fullcalendar-styles', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.9.0/main.min.css', [] );
- wp_enqueue_style( 'font-awesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css', [] );
- wp_enqueue_script( 'fullcalendar-base', 'https://cdn.jsdelivr.net/combine/npm/fullcalendar@5.9.0/main.js', [] );
+ wp_enqueue_script( 'fullcalendar-base', 'https://cdn.jsdelivr.net/combine/npm/fullcalendar@5.11.5/main.js', [] );
wp_enqueue_script( 'popper', 'https://unpkg.com/@popperjs/core@2/dist/umd/popper.min.js', [] );
- wp_enqueue_script( 'tippy', 'https://unpkg.com/tippy.js@6/dist/tippy-bundle.umd.js', [] );
- wp_enqueue_script( 'ux-fullcalendar', WP_CIVICRM_UX_PLUGIN_URL . WP_CIVICRM_UX_PLUGIN_NAME . '/public/js/event-fullcalendar.js', [] );
-
-
- wp_localize_script( 'ux-fullcalendar', 'wp_site_obj',
- array( 'ajax_url' => get_rest_url(),
- 'upload' => $wporg_atts['upload'],
- 'types' => $wporg_atts['types'],
- 'colors' => $colors,
- 'start' => $wporg_atts['start'],
- 'image_id_field' => $atts['image_id_field'],
- 'image_src_field' => $wporg_atts['image_src_field'],
- 'force_login' => $wporg_atts['force_login'],
- 'redirect_after_login' => $redirect_after_login,
- 'extra_fields' => $wporg_atts['extra_fields']));
+ wp_enqueue_script( 'tippy', 'https://unpkg.com/tippy.js@6/dist/tippy-bundle.umd.js', [ 'popper' ] );
+ wp_enqueue_script( 'ux-fullcalendar', WP_CIVICRM_UX_PLUGIN_URL . WP_CIVICRM_UX_PLUGIN_NAME . '/public/js/event-fullcalendar.js', [ 'fullcalendar-base', 'popper', 'tippy' ] );
+
+ $ux_fullcalendar = [
+ 'ajax_url' => get_rest_url(),
+ 'redirect_after_login' => $redirect_after_login,
+ ];
+
+ if(!empty($colors)) {
+ $ux_fullcalendar['colors'] = $colors;
+ }
+
+ $ux_fullcalendar = $ux_fullcalendar + array_filter($wporg_atts);
+
+ if(!empty($ux_fullcalendar['types'])) {
+ $ux_fullcalendar['filterTypes'] = explode(',', $ux_fullcalendar['types']);
+ } else {
+ $options = Event::getFields(FALSE)
+ ->setLoadOptions([ 'name', 'label' ])
+ ->addWhere('name', '=', 'event_type_id')
+ ->addSelect('options')
+ ->execute()
+ ->first()['options'];
+
+ $ux_fullcalendar['filterTypes'] = array_map(
+ fn($_type) => $_type['label'],
+ $options ?: []
+ );
+ }
+
+ wp_localize_script( 'ux-fullcalendar', 'uxFullcalendar', $ux_fullcalendar );
return '
';
}
+
+ public static function getDefaultColor() {
+ return apply_filters( 'ux_event_fullcalendar/default_color', 'transparent' );
+ }
+
+ public static function getDefaultForceLogin() {
+ apply_filters( 'ux_event_fullcalendar/force_login', false );
+ }
}
diff --git a/templates/event-fullcalendar/event-fullcalendar-event-entry.php b/templates/event-fullcalendar/event-fullcalendar-event-entry.php
new file mode 100644
index 0000000..0d769a8
--- /dev/null
+++ b/templates/event-fullcalendar/event-fullcalendar-event-entry.php
@@ -0,0 +1,15 @@
+
+
+
\ No newline at end of file
diff --git a/templates/event-fullcalendar/event-fullcalendar-event.php b/templates/event-fullcalendar/event-fullcalendar-event.php
new file mode 100644
index 0000000..e16bc36
--- /dev/null
+++ b/templates/event-fullcalendar/event-fullcalendar-event.php
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+ = $event['event_type_id:label']; ?>
+
+
= $event['title']; ?>
+
+
+ = $event_time ?>
+
+
+
+
+
+ = $event_location ?>
+
+
+
+
+
+
+
+ Click here to register
+
+
+
+
+
+
+ = !empty($event['description']) ? $event['description'] : 'No event description provided' ?>
+
+
+