diff --git a/code/events_indexer/src/com/turning_leaf_technologies/events/EventsIndexerMain.java b/code/events_indexer/src/com/turning_leaf_technologies/events/EventsIndexerMain.java index 5cded993b9..ea4bb5fcba 100644 --- a/code/events_indexer/src/com/turning_leaf_technologies/events/EventsIndexerMain.java +++ b/code/events_indexer/src/com/turning_leaf_technologies/events/EventsIndexerMain.java @@ -131,6 +131,18 @@ public static void main(String[] args) { indexer.indexEvents(); } + // Native events + getEventsSitesToIndexStmt = aspenConn.prepareStatement("SELECT * from events_indexing_settings"); + eventsSitesRS = getEventsSitesToIndexStmt.executeQuery(); + while (eventsSitesRS.next()) { + NativeEventsIndexer indexer = new NativeEventsIndexer( + eventsSitesRS.getLong("id"), + eventsSitesRS.getInt("numberOfDaysToIndex"), + eventsSitesRS.getBoolean("runFullUpdate"), + solrUpdateServer, aspenConn, logger, serverName); + indexer.indexEvents(); + } + //Index events from other source here try { solrUpdateServer.close(); diff --git a/code/events_indexer/src/com/turning_leaf_technologies/events/NativeEvent.java b/code/events_indexer/src/com/turning_leaf_technologies/events/NativeEvent.java new file mode 100644 index 0000000000..1867d82176 --- /dev/null +++ b/code/events_indexer/src/com/turning_leaf_technologies/events/NativeEvent.java @@ -0,0 +1,211 @@ +package com.turning_leaf_technologies.events; + +import com.turning_leaf_technologies.config.ConfigUtil; +import org.apache.commons.lang.StringUtils; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.zone.ZoneRules; +import java.util.*; + +class NativeEvent { + private final long id; + private final long eventId; + private final int eventType; + private final String startDate; + private final String startTime; + private final int length; + private final String name; + private final String description; + private final String cover; + private final long locationId; + private final String locationCode; + private final HashSet libraries = new HashSet<>(); + private final long sublocationId; + private final Boolean status; + private final Boolean nonPublic; + private final ArrayList fields = new ArrayList(); + + NativeEvent(ResultSet existingEventsRS) throws SQLException{ + this.id = existingEventsRS.getLong("id"); // The event instance ID + this.eventId = existingEventsRS.getLong("eventId"); // The parent event ID + this.eventType = existingEventsRS.getInt("eventTypeId"); + this.startDate = existingEventsRS.getString("date"); + this.startTime = existingEventsRS.getString("time"); + this.length = existingEventsRS.getInt("length"); + this.name = existingEventsRS.getString("title"); + this.description = existingEventsRS.getString("description"); + this.cover = existingEventsRS.getString("cover"); + this.locationId = existingEventsRS.getLong("locationId"); + this.locationCode = existingEventsRS.getString("displayName"); + this.sublocationId = existingEventsRS.getLong("sublocationId"); + this.status = existingEventsRS.getBoolean("status"); + this.nonPublic = existingEventsRS.getBoolean("private"); + } + + void addField(String name, String value, String[] allowableValues, int type, int facet) { + this.fields.add(new EventField(name, value, allowableValues, type, facet)); + } + + void addLibrary(String library) { + libraries.add(library); + } + + HashSet getLibraries() { + return this.libraries; + } + + ArrayList getFields() { + return fields; + } + + long getId() { + return id; + } + + long getParentEventId() { return eventId; } + + int getEventType() { return eventType; } + + public String getStartDate() { + return startDate; + } + + public String getStartTime() { + return startTime; + } + + public int getLength() { + return length; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCover() { + return cover; + } + + public String getCoverUrl(String coverPath) { + return coverPath + "/aspenEvents/" + cover; + } + + public long getLocationId() { + return locationId; + } + + public String getLocationCode() { + return locationCode; + } + + public long getSublocationId() { + return sublocationId; + } + + public Boolean getStatus() { + return status; + } + + public Boolean getNonPublic() { + return nonPublic; + } + + private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private final ZoneId zoneId = ZoneId.systemDefault(); + private final ZoneRules rules = zoneId.getRules(); + private final ZoneOffset zone = rules.getOffset (Instant.now()); + + public Date getStartDateTime(EventsIndexerLogEntry logEntry) { + try { + LocalDateTime date = LocalDateTime.parse(startDate + " " + startTime, dtf); + return Date.from(date.toInstant(zone)); + } catch (DateTimeParseException e) { + logEntry.incErrors("Error parsing end date from " + startDate, e); + return null; + } + } + + public Date getEndDateTime(EventsIndexerLogEntry logEntry) { + try { + LocalDateTime date = LocalDateTime.parse(startDate + " " + startTime, dtf); + LocalDateTime end = date.plusHours(this.length); + Instant endInstant = end.toInstant(zone); + return Date.from(endInstant); + } catch (DateTimeParseException e) { + logEntry.incErrors("Error parsing end date from " + startDate, e); + return null; + } + } + + class EventField { + private final String name; + private final String value; + private final String[] allowableValues; + private final int type; + private final int facet; + + EventField(String name, String value, String[] allowableValues, int type, int facet) { + this.name = name; + this.value = value; + this.allowableValues = allowableValues; + this.type = type; + this.facet = facet; + } + + public String getName() { + return name; + } + + public String getSolrFieldName() { + String sanitized_name = this.name.replaceAll("[^a-zA-Z0-9]", "_"); + switch (this.type) { + case 0: // Text field + return "custom_string_" + sanitized_name; + case 1: // Text area + return "custom_text_" + sanitized_name; + case 2: // Checkbox + return "custom_bool_" + sanitized_name; + case 3: // Select list + case 4: // Email + case 5: // URL + return "custom_string_" + sanitized_name; + } + return sanitized_name; + } + + public String getRawValue() { + return value; + } + + public String getValue() { + if (allowableValues.length > 0 && StringUtils.isNumeric(value)) { + return allowableValues[Integer.parseInt(value)]; + } else { + return value; + } + } + + public String[] getAllowableValues() { + return allowableValues; + } + + public int getType() { + return type; + } + + public int getFacet() { + return facet; + } + } +} diff --git a/code/events_indexer/src/com/turning_leaf_technologies/events/NativeEventsIndexer.java b/code/events_indexer/src/com/turning_leaf_technologies/events/NativeEventsIndexer.java new file mode 100644 index 0000000000..e9a4aa717b --- /dev/null +++ b/code/events_indexer/src/com/turning_leaf_technologies/events/NativeEventsIndexer.java @@ -0,0 +1,214 @@ +package com.turning_leaf_technologies.events; + +import com.turning_leaf_technologies.config.ConfigUtil; +import com.turning_leaf_technologies.strings.AspenStringUtils; +import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.BaseHttpSolrClient; +import org.apache.solr.client.solrj.impl.ConcurrentUpdateHttp2SolrClient; +import org.apache.solr.common.SolrInputDocument; +import org.ini4j.Ini; +import org.json.JSONObject; + +import java.io.IOException; +import java.sql.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Date; +import java.util.zip.CRC32; + +import static java.util.Calendar.DAY_OF_YEAR; + +public class NativeEventsIndexer { + private final long settingsId; + private final int numberOfDaysToIndex; + private final boolean runFullUpdate; + private final Connection aspenConn; + private final EventsIndexerLogEntry logEntry; + private final HashMap eventInstances = new HashMap<>(); + // private final HashSet librariesToShowFor = new HashSet<>(); + private final static CRC32 checksumCalculator = new CRC32(); + private final String serverName; + private final String coverPath; + + private PreparedStatement addEventStmt; + private PreparedStatement deleteEventStmt; + + private final ConcurrentUpdateHttp2SolrClient solrUpdateServer; + + NativeEventsIndexer(long settingsId, int numberOfDaysToIndex, boolean runFullUpdate, ConcurrentUpdateHttp2SolrClient solrUpdateServer, Connection aspenConn, Logger logger, String serverName) { + this.settingsId = settingsId; + this.aspenConn = aspenConn; + this.solrUpdateServer = solrUpdateServer; + this.numberOfDaysToIndex = numberOfDaysToIndex; + this.runFullUpdate = runFullUpdate; + this.serverName = serverName; + + logEntry = new EventsIndexerLogEntry("Native Events", aspenConn, logger); + + Ini configIni = ConfigUtil.loadConfigFile("config.ini", serverName, logger); + coverPath = configIni.get("Site","coverPath"); + + loadEvents(); + } + + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + private final SimpleDateFormat eventDayFormatter = new SimpleDateFormat("yyyy-MM-dd"); + private final SimpleDateFormat eventMonthFormatter = new SimpleDateFormat("yyyy-MM"); + private final SimpleDateFormat eventYearFormatter = new SimpleDateFormat("yyyy"); + private void loadEvents() { + try { + // Calculate date for numberOfDaysToIndex into the future to add to where statement + GregorianCalendar lastDateToIndex = new GregorianCalendar(); + lastDateToIndex.setTime(new Date()); + lastDateToIndex.add(DAY_OF_YEAR, this.numberOfDaysToIndex); + + // Get event instance and event info + PreparedStatement eventsStmt = aspenConn.prepareStatement("SELECT ei.*, e.title, e.description, e.eventTypeId, e.locationId, l.displayName, e.sublocationId, e.cover, e.private FROM event_instance AS ei LEFT JOIN event as e ON e.id = ei.eventID LEFT JOIN location AS l ON e.locationId = l.locationId WHERE ei.date < ?;"); + // Get libraries for this event type + PreparedStatement librariesStmt = aspenConn.prepareStatement("SELECT etl.libraryId, l.subdomain FROM event_type_library AS etl LEFT JOIN library as l ON etl.libraryId = l.libraryId WHERE eventTypeId = ?"); + // Get custom fields + PreparedStatement eventFieldStmt = aspenConn.prepareStatement("SELECT ef.name, ef.allowableValues, ef.type, ef.facetName, eef.value from event_event_field AS eef LEFT JOIN event_field AS ef ON ef.id = eef.eventFieldId WHERE eef.eventId = ?;"); + + eventsStmt.setString(1, dateFormat.format(lastDateToIndex.getTime())); + ResultSet existingEventsRS = eventsStmt.executeQuery(); + + + while (existingEventsRS.next()) { + NativeEvent event = new NativeEvent(existingEventsRS); + librariesStmt.clearParameters(); + librariesStmt.setLong(1, event.getEventType()); + ResultSet librariesFieldsRS = librariesStmt.executeQuery(); + while (librariesFieldsRS.next()) { + event.addLibrary(librariesFieldsRS.getString("subdomain").toLowerCase()); + } + eventFieldStmt.clearParameters(); + eventFieldStmt.setLong(1, event.getParentEventId()); + ResultSet eventFieldsRS = eventFieldStmt.executeQuery(); + while (eventFieldsRS.next()) { + String[] allowableValues = eventFieldsRS.getString("allowableValues").split(", "); + if (allowableValues[0].isEmpty()) { + allowableValues = new String[0]; + } + event.addField(eventFieldsRS.getString("name"), eventFieldsRS.getString("value"), allowableValues, eventFieldsRS.getInt("type"), eventFieldsRS.getInt("facetName")); + } + eventInstances.put(event.getId(), event); + } + } catch (SQLException e) { + logEntry.incErrors("Error loading event instances for Native Events ", e); + } + } + + + void indexEvents() { + + if (runFullUpdate) { + try { + solrUpdateServer.deleteByQuery("type:event AND source:" + this.settingsId); + } catch (BaseHttpSolrClient.RemoteSolrException rse) { + logEntry.incErrors("Solr is not running properly, try restarting " + rse); + System.exit(-1); + } catch (Exception e) { + logEntry.incErrors("Error deleting from index ", e); + } + + for (NativeEvent eventInfo : eventInstances.values()) { + //Add the event to solr + try { + SolrInputDocument solrDocument = new SolrInputDocument(); + solrDocument.addField("id", "nativeEvent_" + settingsId + "_" + eventInfo.getId()); + solrDocument.addField("identifier", eventInfo.getId()); + solrDocument.addField("type", "event_nativeEvent"); + solrDocument.addField("source", settingsId); + + int boost = 1; + solrDocument.addField("last_indexed", new Date()); + solrDocument.addField("last_change", null); + //Make sure the start date exists + Date startDate = eventInfo.getStartDateTime(logEntry); + + solrDocument.addField("start_date", startDate); + if (startDate == null) { + continue; + } + + solrDocument.addField("start_date_sort", startDate.getTime() / 1000); + Date endDate = eventInfo.getEndDateTime(logEntry); + solrDocument.addField("end_date", endDate); + + HashSet eventDays = new HashSet<>(); + HashSet eventMonths = new HashSet<>(); + HashSet eventYears = new HashSet<>(); + Date tmpDate = (Date)startDate.clone(); + + if (tmpDate.equals(endDate) || tmpDate.after(endDate)){ + eventDays.add(eventDayFormatter.format(tmpDate)); + eventMonths.add(eventMonthFormatter.format(tmpDate)); + eventYears.add(eventYearFormatter.format(tmpDate)); + }else { + while (tmpDate.before(endDate)) { + eventDays.add(eventDayFormatter.format(tmpDate)); + eventMonths.add(eventMonthFormatter.format(tmpDate)); + eventYears.add(eventYearFormatter.format(tmpDate)); + tmpDate.setTime(tmpDate.getTime() + 24 * 60 * 60 * 1000); + } + } + //Boost based on start date, we will give preference to anything in the next 30 days + Date today = new Date(); + if (startDate.before(today) || startDate.equals(today)){ + boost += 30; + }else{ + long daysInFuture = (startDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24); + if (daysInFuture > 30){ + daysInFuture = 30; + } + boost += (int) (30 - daysInFuture); + } + solrDocument.addField("event_day", eventDays); + solrDocument.addField("event_month", eventMonths); + solrDocument.addField("event_year", eventYears); + solrDocument.addField("title", eventInfo.getName()); + + // Locations + solrDocument.addField("branch", eventInfo.getLocationCode()); + // Also get sublocation + + // Extra fields + ArrayList extraFields = eventInfo.getFields(); + for (NativeEvent.EventField field : extraFields) { + solrDocument.addField(field.getSolrFieldName(), field.getValue()); // Add as a dynamic field + } + if (eventInfo.getCover() != null && !eventInfo.getCover().isBlank() ) { + solrDocument.addField("image_url", eventInfo.getCoverUrl(coverPath)); + } + + solrDocument.addField("description", eventInfo.getDescription()); + + // Libraries scopes + solrDocument.addField("library_scopes", eventInfo.getLibraries()); + + solrDocument.addField("boost", boost); + solrUpdateServer.add(solrDocument); + + } catch (SolrServerException | IOException e) { + logEntry.incErrors("Error adding event to solr ", e); + } + } + + try { + solrUpdateServer.commit(false, false, true); + } catch (Exception e) { + logEntry.incErrors("Error in final commit while finishing extract, shutting down", e); + logEntry.setFinished(); + logEntry.saveResults(); + System.exit(-3); + } + + logEntry.setFinished(); + } + + } + + +} diff --git a/code/web/RecordDrivers/NativeEventRecordDriver.php b/code/web/RecordDrivers/NativeEventRecordDriver.php new file mode 100644 index 0000000000..64115c2ace --- /dev/null +++ b/code/web/RecordDrivers/NativeEventRecordDriver.php @@ -0,0 +1,431 @@ +valid = true; + } else { + disableErrorHandler(); + try { + require_once ROOT_DIR . '/sys/SearchObject/EventsSearcher.php'; + $searchObject = new SearchObject_EventsSearcher(); + $recordData = $searchObject->getRecord($recordData); + if ($recordData == null) { + $this->valid = false; + } else { + parent::__construct($recordData); + $this->valid = true; + } + } catch (Exception $e) { + $this->valid = false; + } + enableErrorHandler(); + } + } + + public function isValid() { + return $this->valid; + } + + public function getListEntry($listId = null, $allowEdit = true) { + //Use getSearchResult to do the bulk of the assignments + $this->getSearchResult('list', false); + + global $interface; + $interface->assign('eventVendor', 'nativeEvent'); + + //Switch template + return 'RecordDrivers/Events/listEntry.tpl'; + } + + public function getTitle(){ + $title = isset($this->fields['title']) ? $this->fields['title'] : (isset($this->fields['title_display']) ? $this->fields['title_display'] : ''); + if (strpos($title, '|') > 0) { + $title = substr($title, 0, strpos($title, '|')); + } + return trim($title); + } + + public function getSearchResult($view = 'list') { + global $interface; + + $interface->assign('id', $this->getId()); + $interface->assign('bookCoverUrl', $this->getBookcoverUrl('medium')); + $interface->assign('eventUrl', $this->getLinkUrl()); + $interface->assign('title', $this->getTitle()); + if (isset($this->fields['description'])) { + $interface->assign('description', $this->fields['description']); + } else { + $interface->assign('description', ''); + } + if (array_key_exists('reservation_state', $this->fields) && in_array('Cancelled', $this->fields['reservation_state'])) { + $interface->assign('isCancelled', true); + } else { + $interface->assign('isCancelled', false); + } + $allDayEvent = false; + $multiDayEvent = false; + if ($this->isAllDayEvent()){ + $allDayEvent = true; + } + if ($this->isMultiDayEvent()){ + $allDayEvent = false; //if the event is multiple days we don't want to say it's all day + $multiDayEvent = true; + } + $interface->assign('allDayEvent', $allDayEvent); + $interface->assign('multiDayEvent', $multiDayEvent); + $interface->assign('start_date', $this->fields['start_date']); + $interface->assign('end_date', $this->fields['end_date']); + $interface->assign('source', isset($this->fields['source']) ? $this->fields['source'] : ''); + + if (IPAddress::showDebuggingInformation()) { + $interface->assign('summScore', $this->getScore()); + $interface->assign('summExplain', $this->getExplain()); + } + +// require_once ROOT_DIR . '/sys/Events/AssabetSetting.php'; +// $eventSettings = new AssabetSetting; +// $eventSettings->id = $this->getSource(); +// if ($eventSettings->find(true)){ + +// $interface->assign('bypassEventPage', $eventSettings->bypassAspenEventPages); +// } + $interface->assign('isStaff', UserAccount::isStaff()); + $interface->assign('eventsInLists', true); + $interface->assign('bypassEventPage', false); + +// require_once ROOT_DIR . '/sys/Events/EventsUsage.php'; +// $eventsUsage = new EventsUsage(); +// $eventsUsage->type = $this->getType(); +// $eventsUsage->source = $this->getSource(); +// $eventsUsage->identifier = $this->getIdentifier(); +// $eventsUsage->year = date('Y'); +// $eventsUsage->month = date('n'); +// if ($eventsUsage->find(true)) { +// $eventsUsage->timesViewedInSearch++; +// $eventsUsage->update(); +// } else { +// $eventsUsage->timesViewedInSearch = 1; +// $eventsUsage->timesUsed = 0; +// $eventsUsage->insert(); +// } + + return 'RecordDrivers/Events/nativeEvent_result.tpl'; + } + + public function getBookcoverUrl($size = 'small', $absolutePath = false, $type = "nativeEvent_event") { + global $configArray; + + if ($absolutePath) { + $bookCoverUrl = $configArray['Site']['url']; + } else { + $bookCoverUrl = ''; + } + $bookCoverUrl .= "/bookcover.php?id={$this->getUniqueID()}&size={$size}&type={$type}"; + + return $bookCoverUrl; + } + + public function getModule(): string { + return 'Events'; + } + + public function getStaffView() { + global $interface; + return $this->getEventObject(); + } + + public function getDescription() { + if (isset($this->fields['description'])) { + return $this->fields['description']; + } else { + return ''; + } + } + + public function getFullDescription() { + $description = $this->getEventObject(); + return $description->description; + } + + public function getEventTypeFields() { + $keys = array_keys($this->fields); + $typeFields = []; + $html = ""; + foreach ($keys as $key) { + if (str_starts_with($key, 'custom_')) { + $typeFields[$key] = $this->fields[$key]; + } + } + foreach ($typeFields as $key => $value) { + $pattern = '/custom_([a-z]+)_/i'; + $fieldname = preg_replace($pattern, "", $key); + $fieldname = str_replace("_", " ", $fieldname); + $html .= "
  • $fieldname: $value[0]
  • "; + } + return $html; + } + + /** + * Return the unique identifier of this record within the Solr index; + * useful for retrieving additional information (like tags and user + * comments) from the external MySQL database. + * + * @access public + * @return string Unique identifier. + */ + public function getUniqueID() { + return $this->fields['id']; + } + + public function getLinkUrl($absolutePath = false) { + return '/NativeEvents/' . $this->getId() . '/Event'; + } + + public function getExternalUrl($absolutePath = false) { + return null; + } + + public function getAudiences() { + if (array_key_exists('age_group', $this->fields)){ + return $this->fields['age_group']; + } + } + + public function getProgramTypes() { + if (array_key_exists('program_type', $this->fields)){ + return $this->fields['program_type']; + } + } + + public function getBranch() { + return implode(", ", array_key_exists("branch", $this->fields) ? $this->fields['branch'] : []); + } + + public function getRoom() { + return array_key_exists("room", $this->fields) ? $this->fields['room'] : ''; + } + + public function getType() { + return array_key_exists("event_type", $this->fields) ? $this->fields['event_type'] : ''; + } + + public function getIntegration() { + return $this->fields['type']; + } + + public function getSource() { + return $this->fields['source']; + } + + function getEventCoverUrl() { + if (!empty($this->fields['image_url'])) { + global $interface; + return $this->getBookcoverUrl('medium', false, "nativeEvent_eventRecord"); + } + return $this->getBookcoverUrl('medium'); + } + + function getCoverImagePath() { + if (!empty($this->fields['image_url'])) { + return $this->fields['image_url']; + } + return false; + } + + function getEventObject() { + if ($this->eventObject == null) { + $this->eventObject = new EventInstance(); + $this->eventObject->id = $this->getIdentifier(); + if (!$this->eventObject->find(true)) { + $this->eventObject = false; + } + } + return $this->eventObject; + } + + function getStartDateFromDB($id) : ?object { + if ($this->eventObject == null) { + $this->eventObject = new EventInstance(); + $this->eventObject->$id; + + if (!$this->eventObject->find(true)) { + $this->eventObject = false; + } + } + $data = $this->eventObject; + + try { + $startDate = new DateTime($data->date . " " . $data->time); + $startDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + return $startDate; + } catch (Exception $e) { + return null; + } + + } + + function getTitleFromDB($id) { + if ($this->eventObject == null) { + $this->eventObject = new Event(); + $this->eventObject->externalId; + + if (!$this->eventObject->find(true)) { + $this->eventObject = false; + } + } + $data = $this->eventObject; + + return $data->title; + } + + private function getIdentifier() { + return $this->fields['identifier']; + } + + public function getStartDate() { + try { + //Need to specify timezone since we start as a timstamp + $startDate = new DateTime($this->fields['start_date']); + $startDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + return $startDate; + } catch (Exception $e) { + return null; + } + } + + public function getEndDate() { + try { + //Need to specify timezone since we start as a timstamp + $endDate = new DateTime($this->fields['end_date']); + $endDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + return $endDate; + } catch (Exception $e) { + return null; + } + } + + public function isAllDayEvent() { + try { + $start = new DateTime($this->fields['start_date']); + $end = new DateTime($this->fields['end_date']); + + $interval = $start->diff($end); + + if ($interval->h == 24 || ($interval->i == 0 && $interval->h == 0)){ //some events don't last an hour + return 1; + } + return 0; + } catch (Exception $e) { + return null; + } + } + + public function isMultiDayEvent() { + try { + $start = new DateTime($this->fields['start_date']); + $end = new DateTime($this->fields['end_date']); + + $interval = $start->diff($end); + + if ($interval->d > 0){ + return 1; + } + return 0; + } catch (Exception $e) { + return null; + } + } + + public function isRegistrationRequired(): bool { + if (array_key_exists("registration_required", $this->fields) && $this->fields['registration_required'] == "Yes") { + return true; + } else { + return false; + } + } + + public function inEvents() { + if (UserAccount::isLoggedIn()) { + return UserAccount::getActiveUserObj()->inUserEvents($this->getId()); + }else{ + return false; + } + } + + public function isRegisteredForEvent() { + if (UserAccount::isLoggedIn()) { + return UserAccount::getActiveUserObj()->isRegistered($this->getId()); + }else{ + return false; + } + } + + public function getSpotlightResult(CollectionSpotlight $collectionSpotlight, string $index) { + $result = parent::getSpotlightResult($collectionSpotlight, $index); + if ($collectionSpotlight->style == 'text-list') { + global $interface; + $interface->assign('start_date', $this->fields['start_date']); + $interface->assign('end_date', $this->fields['end_date']); + $result['formattedTextOnlyTitle'] = $interface->fetch('RecordDrivers/Events/formattedTextOnlyTitle.tpl'); + } + + return $result; + } + + public function getBypassSetting() { + require_once ROOT_DIR . '/sys/Events/AssabetSetting.php'; + $eventSettings = new AssabetSetting(); + $eventSettings->id = $this->getSource(); + if ($eventSettings->find(true)){ + return $eventSettings->bypassAspenEventPages; + } + + return false; + } + + public function getAllowInListsSetting() { + require_once ROOT_DIR . '/sys/Events/AssabetSetting.php'; + $eventSettings = new AssabetSetting(); + $eventSettings->id = $this->getSource(); + if ($eventSettings->find(true)){ + return $eventSettings->eventsInLists; + } + + return false; + } + + public function getSummaryInformation() { + return [ + 'id' => $this->getUniqueID(), + 'shortId' => $this->getIdentifier(), + 'recordtype' => 'event', + 'image' => $this->getBookcoverUrl('medium'), + 'title' => $this->getTitle(), + 'description' => strip_tags($this->getDescription()), + 'isAllDay' => $this->isAllDayEvent(), + 'start_date' => $this->getStartDate(), + 'end_date' => $this->getEndDate(), + 'registration_required' => $this->isRegistrationRequired(), + 'bypass' => $this->getBypassSetting(), + 'url' => null, + 'source' => 'nativeEvents', + 'author' => null, + 'format' => null, + 'ratingData' => null, + 'language' => null, + 'publisher' => '', + 'length' => '', + 'titleURL' => null, + ]; + } +} \ No newline at end of file diff --git a/code/web/index.php b/code/web/index.php index df3a2eee43..0ca45fb201 100644 --- a/code/web/index.php +++ b/code/web/index.php @@ -1107,7 +1107,7 @@ function loadModuleActionId() { } /** IndexingProfile[] $indexingProfiles */ global $indexingProfiles; /** SideLoad[] $sideLoadSettings */ global $sideLoadSettings; - $allRecordModules = "OverDrive|GroupedWork|Record|ExternalEContent|Person|Library|RBdigital|Hoopla|RBdigitalMagazine|CloudLibrary|Files|Axis360|WebBuilder|ProPay|CourseReserves|Springshare|LibraryMarket|Communico|PalaceProject|Assabet"; + $allRecordModules = "OverDrive|GroupedWork|Record|ExternalEContent|Person|Library|RBdigital|Hoopla|RBdigitalMagazine|CloudLibrary|Files|Axis360|WebBuilder|ProPay|CourseReserves|Springshare|LibraryMarket|Communico|PalaceProject|Assabet|NativeEvents"; foreach ($indexingProfiles as $profile) { $allRecordModules .= '|' . $profile->recordUrlComponent; } diff --git a/code/web/interface/themes/responsive/Admin/propertiesList.tpl b/code/web/interface/themes/responsive/Admin/propertiesList.tpl index 68458e5492..43801d6561 100644 --- a/code/web/interface/themes/responsive/Admin/propertiesList.tpl +++ b/code/web/interface/themes/responsive/Admin/propertiesList.tpl @@ -126,6 +126,8 @@ {$propValue|escape} {elseif $property.type == 'date'} {$propValue|date_format} + {elseif $property.type == 'time'} + {$propValue|date_format:"%I:%M %p"} {elseif $property.type == 'timestamp'} {if $propValue == 0} {if empty($property.unsetLabel)} diff --git a/code/web/interface/themes/responsive/DataObjectUtil/oneToMany.tpl b/code/web/interface/themes/responsive/DataObjectUtil/oneToMany.tpl index 2229c99428..76ae8da6f4 100644 --- a/code/web/interface/themes/responsive/DataObjectUtil/oneToMany.tpl +++ b/code/web/interface/themes/responsive/DataObjectUtil/oneToMany.tpl @@ -8,7 +8,7 @@ {translate text="Sort" isAdminFacing=true} {/if} {foreach from=$property.structure item=subProperty} - {if (in_array($subProperty.type, array('text', 'regularExpression', 'multilineRegularExpression', 'enum', 'date', 'checkbox', 'integer', 'textarea', 'html', 'dynamic_label', 'multiSelect')) || ($subProperty.type == 'multiSelect' && $subProperty.listStyle == 'checkboxList')) && empty($subProperty.hideInLists) } + {if (in_array($subProperty.type, array('text', 'regularExpression', 'multilineRegularExpression', 'enum', 'date', 'time', 'checkbox', 'integer', 'textarea', 'html', 'dynamic_label', 'multiSelect')) || ($subProperty.type == 'multiSelect' && $subProperty.listStyle == 'checkboxList')) && empty($subProperty.hideInLists) } {translate text=$subProperty.label isAdminFacing=true} {/if} {/foreach} @@ -29,7 +29,7 @@ {/if} {foreach from=$property.structure item=subProperty} - {if in_array($subProperty.type, array('text', 'regularExpression', 'enum', 'date', 'checkbox', 'integer', 'textarea', 'html', 'dynamic_label', 'multiSelect')) && empty($subProperty.hideInLists)} + {if in_array($subProperty.type, array('text', 'regularExpression', 'enum', 'date', 'time', 'checkbox', 'integer', 'textarea', 'html', 'dynamic_label', 'multiSelect')) && empty($subProperty.hideInLists)} {assign var=subPropName value=$subProperty.property} {assign var=subPropValue value=$subObject->$subPropName} @@ -37,6 +37,8 @@ {elseif $subProperty.type=='date'} + {elseif $subProperty.type=='time'} + {elseif $subProperty.type=='dynamic_label'} {$subPropValue|escape} {elseif $subProperty.type=='textarea' || $subProperty.type=='multilineRegularExpression'} @@ -186,13 +188,15 @@ {/if} {foreach from=$property.structure item=subProperty} {if empty($subProperty.hideInLists)} - {if in_array($subProperty.type, array('text', 'regularExpression','multilineRegularExpression', 'enum', 'date', 'checkbox', 'integer', 'textarea', 'html', 'dynamic_label')) } + {if in_array($subProperty.type, array('text', 'regularExpression','multilineRegularExpression', 'enum', 'date', 'time', 'checkbox', 'integer', 'textarea', 'html', 'dynamic_label')) } newRow += ""; {assign var=subPropName value=$subProperty.property} {if $subProperty.type=='text' || $subProperty.type=='regularExpression' || $subProperty.type=='multilineRegularExpression' || $subProperty.type=='integer' || $subProperty.type=='textarea'|| $subProperty.type=='html'} newRow += ""; {elseif $subProperty.type=='date'} newRow += ""; + {elseif $subProperty.type=='time'} + newRow += ""; {elseif $subProperty.type=='dynamic_label'} newRow += "" {elseif $subProperty.type=='checkbox'} diff --git a/code/web/interface/themes/responsive/DataObjectUtil/property.tpl b/code/web/interface/themes/responsive/DataObjectUtil/property.tpl index 04e0c2fb77..7e8203eb57 100644 --- a/code/web/interface/themes/responsive/DataObjectUtil/property.tpl +++ b/code/web/interface/themes/responsive/DataObjectUtil/property.tpl @@ -224,7 +224,7 @@ {if !empty($property.affectsLiDA)} {translate text="Aspen LiDA also uses this setting" isAdminFacing=true}{/if} {if !empty($property.note)} {$property.note}{/if} {elseif $property.type == 'integer'} - + {if !empty($property.forcesReindex)} {translate text="Updating this setting causes a nightly reindex" isAdminFacing=true}{/if} {if !empty($property.affectsLiDA)} {translate text="Aspen LiDA also uses this setting" isAdminFacing=true}{/if} {if !empty($property.note)} {$property.note}{/if} @@ -415,12 +415,13 @@ {elseif $property.type == 'multiemail'} {elseif $property.type == 'date'} - + {elseif $property.type == 'dayMonth'} {include file="DataObjectUtil/dayMonthPicker.tpl"} {elseif $property.type == 'partialDate'} {include file="DataObjectUtil/partialDate.tpl"} - + {elseif $property.type == 'time'} + {elseif $property.type == 'textarea' || $property.type == 'html' || $property.type == 'markdown' || $property.type == 'javascript' || $property.type == 'crSeparated' || $property.type == 'multilineRegularExpression'} {include file="DataObjectUtil/textarea.tpl"} {if !empty($property.forcesReindex)} {translate text="Updating this setting causes a nightly reindex" isAdminFacing=true}{/if} diff --git a/code/web/interface/themes/responsive/NativeEvents/event.tpl b/code/web/interface/themes/responsive/NativeEvents/event.tpl new file mode 100644 index 0000000000..c9b847feb0 --- /dev/null +++ b/code/web/interface/themes/responsive/NativeEvents/event.tpl @@ -0,0 +1,155 @@ +{*Title Div*} +
    +
    +
    +

    {$recordDriver->getTitle()}

    +
    +
    +
    +{*Content Div*} +
    + {*Left Panel Content*} +
    + {if !empty($recordDriver->getEventCoverUrl())} +
    +
    + {$recordDriver->getTitle()|escape} +
    +
    + {/if} + {if !empty($recordDriver->getAudiences())} +
    +
    + {translate text="Audience" isPublicFacing=true} +
    +
    + {foreach from=$recordDriver->getAudiences() item=audience} + + {/foreach} +
    +
    + {/if} + {if !empty($recordDriver->getProgramTypes())} +
    +
    + {translate text="Program Type" isPublicFacing=true} +
    +
    + {foreach from=$recordDriver->getProgramTypes() item=type} +
    + {$type} +
    + {/foreach} +
    +
    + {/if} +
    + + {*Content Right of Panel*} +
    + {*Row for Information and Registration/Your Events Button*} +
    +
    +
      + {if $recordDriver->isAllDayEvent()} +
    • {translate text="Date: " isPublicFacing=true}{$recordDriver->getStartDate()|date_format:"%A %B %e, %Y"}
    • +
    • {translate text="Time: All Day Event" isPublicFacing=true}
    • + {elseif $recordDriver->isMultiDayEvent()} +
    • {translate text="Start Date: " isPublicFacing=true}{$recordDriver->getStartDate()|date_format:"%a %b %e, %Y %l:%M%p"}
    • +
    • {translate text="End Date: " isPublicFacing=true}{$recordDriver->getEndDate()|date_format:"%a %b %e, %Y %l:%M%p"}
    • + {else} +
    • {translate text="Date: " isPublicFacing=true}{$recordDriver->getStartDate()|date_format:"%A %B %e, %Y"}
    • +
    • {translate text="Time: " isPublicFacing=true}{$recordDriver->getStartDate()|date_format:"%l:%M %p"} to {$recordDriver->getEndDate()|date_format:"%l:%M %p"}
    • + {/if} +
    • {translate text="Branch: " isPublicFacing=true}{$recordDriver->getBranch()}
    • + {if !empty($recordDriver->getRoom())} +
    • {translate text="Room: " isPublicFacing=true}{$recordDriver->getRoom()}
    • + {/if} + {if !empty($recordDriver->getEventTypeFields())} + {$recordDriver->getEventTypeFields()} + {/if} +
    +
    +
    + {if $recordDriver->inEvents()} + {if $recordDriver->isRegistrationRequired()} + +
    + {else} + {translate text="In Your Events" isPublicFacing=true} + {/if} + {else} + {if $recordDriver->isRegistrationRequired()} +
    + {if !empty($recordDriver->getRegistrationModalBody())} + {translate text="Registration Information" isPublicFacing=true} + + {else} + {translate text="Registration Information" isPublicFacing=true} + {/if} + {if empty($offline) || $enableEContentWhileOffline} + {translate text="Add to Your Events" isPublicFacing=true} + {/if} +
    + {elseif empty($offline) || $enableEContentWhileOffline} + {translate text="Add to Your Events" isPublicFacing=true} + {/if} + {/if} +
    +
    + {*column for tool buttons & event description*} +
    +
    + {translate text="More Info" isPublicFacing=true} + {if $isStaff && $eventsInLists == 1 || $eventsInLists == 2} + + {/if} +
    +
    + {include file="Events/share-tools.tpl" eventUrl=$recordDriver->getExternalUrl()} +
    +
    +
    + {$recordDriver->getFullDescription()} +
    +
    +
    + +{*Staff View Div*} +{if !empty($loggedIn) && (in_array('Administer Assabet Settings', $userPermissions))} +
    +
    +
    + +
    +
    +

    {translate text=Staff isPublicFacing=true}

    +
    +
    +
    +
    +
    +

    {translate text="Assabet Event API response" isPublicFacing=true}

    +
    {$recordDriver->getStaffView()|print_r}
    +
    +
    +
    +
    +
    +{/if} diff --git a/code/web/interface/themes/responsive/RecordDrivers/Events/nativeEvent_result.tpl b/code/web/interface/themes/responsive/RecordDrivers/Events/nativeEvent_result.tpl new file mode 100644 index 0000000000..0fa4d676c5 --- /dev/null +++ b/code/web/interface/themes/responsive/RecordDrivers/Events/nativeEvent_result.tpl @@ -0,0 +1,127 @@ +{strip} +
    +
    + {if !empty($showCovers)} + + {/if} + +
    {* May turn out to be more than one situation to consider here *} + {* Title Row *} + + + +
    +
    {translate text="Date" isPublicFacing=true}
    +
    + {if $allDayEvent} + {$start_date|date_format:"%a %b %e, %Y"}{translate text=" - All Day Event" isPublicFacing=true} + {elseif $multiDayEvent} + {$start_date|date_format:"%a %b %e, %Y %l:%M%p"} to {$end_date|date_format:"%a %b %e, %Y %l:%M%p"} + {else} + {$start_date|date_format:"%a %b %e, %Y from %l:%M%p"} to {$end_date|date_format:"%l:%M%p"} + {/if} + {if !empty($isCancelled)} +  {translate text="Cancelled" isPublicFacing=true} + {/if} +
    +
    +
    +
    {translate text="Location" isPublicFacing=true}
    +
    + {$recordDriver->getBranch()} +
    + {* Register Button *} +
    + {if $recordDriver->inEvents()} + {if $recordDriver->isRegistrationRequired()} + +
    + {else} + +
    + {/if} + {else} + {if $recordDriver->isRegistrationRequired()} +
    +
    + {if !empty($recordDriver->getRegistrationModalBody())} + {translate text="Registration Information" isPublicFacing=true} + + {else} + {translate text="Registration Information" isPublicFacing=true} + {/if} + {if empty($offline) || $enableEContentWhileOffline} + {translate text="Add to Your Events" isPublicFacing=true} + {/if} +
    +
    +
    + {elseif empty($offline) || $enableEContentWhileOffline} + +
    + {/if} + {/if} +
    +
    + + {* Description Section *} + {if !empty($description)} +
    +
    {translate text="Description" isPublicFacing=true}
    + +
    + +
    + {* Hide in mobile view *} + +
    + {/if} + +
    +
    + {include file='Events/result-tools-horizontal.tpl' recordUrl=$eventUrl showMoreInfo=true} +
    +
    +
    +
    +
    +{/strip} diff --git a/code/web/interface/themes/responsive/js/aspen.js b/code/web/interface/themes/responsive/js/aspen.js index 60997e44fe..aaaf23be13 100644 --- a/code/web/interface/themes/responsive/js/aspen.js +++ b/code/web/interface/themes/responsive/js/aspen.js @@ -12225,6 +12225,489 @@ AspenDiscovery.EContent = (function(){ } }(AspenDiscovery.EContent)); +AspenDiscovery.Events = (function(){ + return { + trackUsage: function (id) { + var ajaxUrl = Globals.path + "/Events/JSON?method=trackUsage&id=" + id; + $.getJSON(ajaxUrl); + }, + + //For native events + getEventTypesForLocation: function(locationId) { + var url = Globals.path + '/Events/AJAX'; + var params = { + method: 'getEventTypeIdsForLocation', + locationId: locationId + }; + + $.getJSON(url, params, function (data) { + if (data.success) { + if (data.eventTypeIds.length > 0) { + $("#eventTypeIdSelect option").each(function () { + if (!data.eventTypeIds.includes($(this).val())) { + $(this).attr('disabled', 'disabled'); + $(this).removeAttr('selected'); + $(this).hide(); + } else { + $(this).removeAttr('disabled'); + $(this).show(); + } + }); + $("#propertyRoweventTypeId").show(); + } else { + AspenDiscovery.showMessage(data.title, data.message); + $("#eventTypeIdSelect option").each(function () { + $(this).attr('disabled', 'disabled'); + $(this).removeAttr('selected'); + }); + $("#propertyRoweventTypeId").hide(); + $("#propertyRowtitle").hide(); + $("#propertyRowinfoSection").hide(); + } + } else { + AspenDiscovery.showMessage('An error occurred ', data.message); + } + }); + }, + + getEventTypeFields: function (eventTypeId) { + var url = Globals.path + '/Events/AJAX'; + var params = { + method: 'getEventTypeFields', + eventTypeId: eventTypeId + }; + + $.getJSON(url, params, function (data) { + if (data.success) { + eventType = data.eventType; + $("#title").val(eventType.title); + if (!eventType.titleCustomizable) { + $("#title").attr('readonly', 'readonly'); + } else { + $("#title").removeAttr('readonly'); + } + $("#description").val(eventType.description); + if (!eventType.descriptionCustomizable) { + $("#description").attr('readonly', 'readonly'); + } else { + $("#description").removeAttr('readonly'); + } + $("#importFile-label-cover").val(eventType.cover); + if (!eventType.coverCustomizable) { + $("#importFile-label-cover").attr('readonly', 'readonly'); + } else { + $("#importFile-label-cover").removeAttr('readonly'); + } + $("#eventLength").val(eventType.eventLength); + if (!eventType.lengthCustomizable) { + $("#eventLength").attr('readonly', 'readonly'); + } else { + $("#eventLength").removeAttr('readonly'); + } + $("#accordion_body_Fields_for_this_Event_Type .panel-body").html(data.typeFields); + $("#propertyRowtitle").show(); + $("#propertyRowinfoSection").show(); + $("#propertyRowinfoSection .propertyRow").show(); + } else { + AspenDiscovery.showMessage('An error occurred ', data.message); + } + }); + return false; + }, + + updateRecurrenceOptions: function (startDate) { + startDate = moment(startDate); + if (startDate.isValid()) { + startDay = startDate.format("dddd"); + var date = startDate.format("MMMM D"); + var weekOfMonth = AspenDiscovery.Events.getWeekofMonth(startDate); + weekOfMonth = moment.localeData().ordinal(weekOfMonth); // Format as ordinal + $("#recurrenceOptionSelect option[value=3]").text("Weekly on " + startDay + "s"); + $("#recurrenceOptionSelect option[value=4]").text("Monthly on the " + weekOfMonth + " " + startDay); + $("#recurrenceOptionSelect option[value=5]").text("Annually on " + date); + AspenDiscovery.Events.calculateEndTime(); + AspenDiscovery.Events.calculateRecurrenceDates(); + } + return false; + }, + + getWeekofMonth: function (date) { + return date.week() - date.startOf('month').week() + 1; + }, + + calculateEndTime: function () { + console.log("Calculating end time"); + var startDate = moment($("#startDate").val()); + var startTime = $("#startTime").val(); + var length = $("#eventLength").val(); + if (startDate && startDate.isValid() && startTime && startTime.length && length && length.length) { + var timeParts = startTime.split(":"); + startDate.hour(timeParts[0]).minute(timeParts[1]); + startDate.add(length, 'h'); + $("#endDate").val(startDate.format("YYYY-MM-DD")); + $("#endTime").val(startDate.format("HH:mm")); + } + return false; + }, + + calculateRecurrenceDates: function () { + + var endDate; + var recurrenceTotal; + var count = 0; + var useEndDate = false; + if ($("#endOptionSelect").val() == "1" && $("#recurrenceEnd").val()) { + endDate = moment($("#recurrenceEnd").val()); + if (!endDate.isValid()) { + return false; + } + useEndDate = true; + } else if ($("#endOptionSelect").val() == "2" && $("#recurrenceCount").val() > 0) { + recurrenceTotal = $("#recurrenceCount").val(); + } else { + return false; // We need either the end date or the number of recurrences to be set or else we can't calculate dates yet + } + + var date = moment($("#startDate").val()); + if (!date.isValid()) { + date = moment(); // Use today's date if there's no start date + } + var originalStart = date.format(); + + var datesPreview = []; + var dates = []; + var frequency = $("#recurrenceFrequencySelect").val(); + var interval = $("#recurrenceInterval").val() || 1; // Assume interval is 1 if not set + + function processMonthlyRepeat() { + tempDate = date.format(); // Keep original date + if (repeatBasedOnDate) { + if (dayNumber <= date.daysInMonth()) { + date.date(dayNumber); + } else { + date.add(1, 'M'); + return false; // Don't generate if the day doesn't exist in the month + } + } else { + endOfMonth = date.endOf("month").format(); + startOfMonth = date.startOf("Month").format(); + if (date.day(weekDay).isBefore(startOfMonth)) { + date.add(1, 'w'); + } + if (weekNumber > 0) { + date.add(weekNumber - 1, 'w'); + } else { // Handle last week of the month + date.add(4, 'w'); + if (date.isAfter(endOfMonth)) { + date.subtract(7, 'd'); + } + } + if (date.isBefore(originalStart)) { + date.add(1, 'M'); // If it's before the start date, add a month and try again + return false; + } + if (date.isAfter(endOfMonth)) { + date = moment(tempDate).add(interval, 'M'); + return false; // Don't generate if the day doesn't exist in the month + } + } + if (!repeatBasedOnDate && offset != 0) { + date.add(offset, 'd'); + if (useEndDate && date.isAfter(endDate)) { + date = moment(tempDate).add(interval, 'M'); + return false; + } + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date = moment(tempDate); + } else { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + } + date.add(interval, 'M'); + return true; + } + + switch (frequency) { + // daily + case '1': + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'd'); + } + } else { + while (count < recurrenceTotal) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'd'); + count++; + } + } + break; + case '2': + // weekly + var days = []; + $("#propertyRowweekDays input:checked").each(function () { + days.push($(this).val()); + }); + if (days.length) { + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + for (i = 0; i < days.length; i++) { + date.day(days[i]); // Set the date to the matching day in the same week + if (date.isBefore(originalStart)) { + date.add(1, 'w'); // If it's before the start date, add a week + } + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + } + date.add(interval, 'w'); + } + } else { + while (count < recurrenceTotal) { + for (i = 0; i < days.length && count < recurrenceTotal; i++) { + date.day(days[i]); // Set the date to the matching day in the same week + if (date.isBefore(originalStart)) { + date.add(1, 'w'); // If it's before the start date, add a week + } + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + count++; + } + date.add(interval, 'w'); + } + } + } else { + return false; //No days selected + } + break; + case '3': + // monthly + var repeatBasedOnDate = $("#monthlyOptionSelect").val() == "2"; + var dayNumber = $("#monthDate").val() || date.format('D'); // If not set, use startDate + var weekNumber = $("#weekNumberSelect").val() || AspenDiscovery.Events.getWeekofMonth(date); + var weekDay = $("#monthDaySelect").val() || date.format('d'); + var endOfMonth; + var startOfMonth; + var tempDate; + var offset = $("#monthOffset").val(); + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + processMonthlyRepeat(); + } + } else { + while (count < recurrenceTotal) { + if (processMonthlyRepeat()) { + count++; // Only count if the date wasn't skipped + } + } + } + break; + + case '4': + // yearly + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'y'); + } + } else { + while (count < recurrenceTotal) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'y'); + count++; + } + } + } + $("#datesPreview").html(datesPreview.join("
    ")); + $("#dates").val(dates); + return false; + }, + + collapsePanel: function (panelSelector) { + $(panelSelector + " .panel-title a").removeClass('expanded').addClass('collapsed').attr("aria-expanded", "false"); + $(panelSelector + " .panel").removeClass('active').attr("aria-expanded", "false"); + $(panelSelector + " .accordion_body").removeClass('in').hide(); + $(panelSelector + " .accordion_body").removeClass('in').hide(); + }, + + expandPanel: function (panelSelector) { + $(panelSelector + " .panel-title a").removeClass('collapsed').addClass('expanded').attr("aria-expanded", "true"); + $(panelSelector + " .panel").addClass('active').attr("aria-expanded", "true"); + $(panelSelector + " .accordion_body").addClass('in').show(); + }, + + toggleRecurrenceSections: function (recurrence) { + + function resetRecurrenceSections() { + $("#propertyRowfrequencySection").hide(); + $("#propertyRowweeklySection").hide(); + $("#propertyRowmonthlySection").hide(); + $("#propertyRowrepeatEndsSection").hide(); + $("#propertyRowdatesPreview").hide(); + $("#propertyRowweekDays input").prop("checked", false); + $("#propertyRowweekNumber option").prop("selected", false); + $("#propertyRowmonthDay option").prop("selected", false); + AspenDiscovery.Events.collapsePanel("#accordion_Repeat_Frequency"); + } + var startDate = moment($("#startDate").val()); // Check what happens if invalid date + var dayNumber = startDate.format('d'); + var dayOfWeek = startDate.day(); + var weekOfMonth = AspenDiscovery.Events.getWeekofMonth(startDate); + switch (recurrence) { + case '1': + // Does not repeat + resetRecurrenceSections(); + break; + case '2': + // Daily + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=1]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowdatesPreview").show(); + $("#propertyRowrepeatEndsSection").show(); + break; + case '3': + // Weekly on same day of week + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=2]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + // Show weekly with specific day selected based on startDate + $("#propertyRowweekDays input[value=" + dayOfWeek + "]").prop("checked", true); + $("#propertyRowweeklySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '4': + // Monthly on same day of week + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=3]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + // Show monthly with specific day based on startdate + $("#propertyRowweekNumber option[value=" + weekOfMonth + "]").prop("selected", true); + $("#propertyRowmonthDay option[value=" + dayOfWeek + "]").prop("selected", true); + $("#propertyRowweekNumber").show(); + $("#propertyRowmonthDay").show(); + $("#propertyRowmonthDate").hide(); + $("#propertyRowmonthlySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '5': + // Annually + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=4]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '6': + // Every week day + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=2]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowweekDays input[value!=6][value!=0]").prop("checked", true); + $("#propertyRowweeklySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '7': + // Custom - nothing preset + resetRecurrenceSections(); + AspenDiscovery.Events.expandPanel("#accordion_Repeat_Frequency"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowrepeatEndsSection").show(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + }, + + toggleMonthlyOptions: function (option) { + switch (option) { + case '1': + // By day of week + $("#propertyRowweekNumber").show(); + $("#propertyRowmonthDay").show(); + $("#propertyRowmonthDate").hide(); + $("#propertyRowmonthOffset").show(); + break; + case '2': + // By date + $("#propertyRowweekNumber").hide(); + $("#propertyRowmonthDay").hide(); + $("#propertyRowmonthDate").show(); + $("#propertyRowmonthOffset").hide(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + }, + + toggleEndOptions: function (option) { + switch (option) { + case '1': + // By date + $("#propertyRowrecurrenceEnd").show(); + $("#propertyRowrecurrenceCount").hide(); + break; + case '2': + // By count + $("#propertyRowrecurrenceEnd").hide(); + $("#propertyRowrecurrenceCount").show(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + }, + + toggleSectionsByFrequency: function (option) { + + function resetSections() { + $("#propertyRowweeklySection").hide(); + $("#propertyRowmonthlySection").hide(); + AspenDiscovery.Events.collapsePanel("#propertyRowmonthlySection"); + AspenDiscovery.Events.collapsePanel("#propertyRowweeklySection"); + } + + switch (option) { + case '1': + // Daily + // No extra options + resetSections(); + break; + case '2': + // Weekly + resetSections(); + $("#propertyRowweeklySection").show(); + AspenDiscovery.Events.expandPanel("#propertyRowweeklySection"); + break; + case '3': + // Monthly + resetSections(); + $("#propertyRowmonthlySection").show(); + AspenDiscovery.Events.expandPanel("#propertyRowmonthlySection"); + break; + case '4': + // Annually + // No extra options + resetSections(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + } + }; +}(AspenDiscovery.Events || {})); AspenDiscovery.GroupedWork = (function(){ return { hasTableOfContentsInRecord: false, diff --git a/code/web/interface/themes/responsive/js/aspen/events.js b/code/web/interface/themes/responsive/js/aspen/events.js index 45b7d7557a..7e5c26942e 100644 --- a/code/web/interface/themes/responsive/js/aspen/events.js +++ b/code/web/interface/themes/responsive/js/aspen/events.js @@ -3,6 +3,481 @@ AspenDiscovery.Events = (function(){ trackUsage: function (id) { var ajaxUrl = Globals.path + "/Events/JSON?method=trackUsage&id=" + id; $.getJSON(ajaxUrl); + }, + + //For native events + getEventTypesForLocation: function(locationId) { + var url = Globals.path + '/Events/AJAX'; + var params = { + method: 'getEventTypeIdsForLocation', + locationId: locationId + }; + + $.getJSON(url, params, function (data) { + if (data.success) { + if (data.eventTypeIds.length > 0) { + $("#eventTypeIdSelect option").each(function () { + if (!data.eventTypeIds.includes($(this).val())) { + $(this).attr('disabled', 'disabled'); + $(this).removeAttr('selected'); + $(this).hide(); + } else { + $(this).removeAttr('disabled'); + $(this).show(); + } + }); + $("#propertyRoweventTypeId").show(); + } else { + AspenDiscovery.showMessage(data.title, data.message); + $("#eventTypeIdSelect option").each(function () { + $(this).attr('disabled', 'disabled'); + $(this).removeAttr('selected'); + }); + $("#propertyRoweventTypeId").hide(); + $("#propertyRowtitle").hide(); + $("#propertyRowinfoSection").hide(); + } + } else { + AspenDiscovery.showMessage('An error occurred ', data.message); + } + }); + }, + + getEventTypeFields: function (eventTypeId) { + var url = Globals.path + '/Events/AJAX'; + var params = { + method: 'getEventTypeFields', + eventTypeId: eventTypeId + }; + + $.getJSON(url, params, function (data) { + if (data.success) { + eventType = data.eventType; + $("#title").val(eventType.title); + if (!eventType.titleCustomizable) { + $("#title").attr('readonly', 'readonly'); + } else { + $("#title").removeAttr('readonly'); + } + $("#description").val(eventType.description); + if (!eventType.descriptionCustomizable) { + $("#description").attr('readonly', 'readonly'); + } else { + $("#description").removeAttr('readonly'); + } + $("#importFile-label-cover").val(eventType.cover); + if (!eventType.coverCustomizable) { + $("#importFile-label-cover").attr('readonly', 'readonly'); + } else { + $("#importFile-label-cover").removeAttr('readonly'); + } + $("#eventLength").val(eventType.eventLength); + if (!eventType.lengthCustomizable) { + $("#eventLength").attr('readonly', 'readonly'); + } else { + $("#eventLength").removeAttr('readonly'); + } + $("#accordion_body_Fields_for_this_Event_Type .panel-body").html(data.typeFields); + $("#propertyRowtitle").show(); + $("#propertyRowinfoSection").show(); + $("#propertyRowinfoSection .propertyRow").show(); + } else { + AspenDiscovery.showMessage('An error occurred ', data.message); + } + }); + return false; + }, + + updateRecurrenceOptions: function (startDate) { + startDate = moment(startDate); + if (startDate.isValid()) { + startDay = startDate.format("dddd"); + var date = startDate.format("MMMM D"); + var weekOfMonth = AspenDiscovery.Events.getWeekofMonth(startDate); + weekOfMonth = moment.localeData().ordinal(weekOfMonth); // Format as ordinal + $("#recurrenceOptionSelect option[value=3]").text("Weekly on " + startDay + "s"); + $("#recurrenceOptionSelect option[value=4]").text("Monthly on the " + weekOfMonth + " " + startDay); + $("#recurrenceOptionSelect option[value=5]").text("Annually on " + date); + AspenDiscovery.Events.calculateEndTime(); + AspenDiscovery.Events.calculateRecurrenceDates(); + } + return false; + }, + + getWeekofMonth: function (date) { + return date.week() - date.startOf('month').week() + 1; + }, + + calculateEndTime: function () { + console.log("Calculating end time"); + var startDate = moment($("#startDate").val()); + var startTime = $("#startTime").val(); + var length = $("#eventLength").val(); + if (startDate && startDate.isValid() && startTime && startTime.length && length && length.length) { + var timeParts = startTime.split(":"); + startDate.hour(timeParts[0]).minute(timeParts[1]); + startDate.add(length, 'h'); + $("#endDate").val(startDate.format("YYYY-MM-DD")); + $("#endTime").val(startDate.format("HH:mm")); + } + return false; + }, + + calculateRecurrenceDates: function () { + + var endDate; + var recurrenceTotal; + var count = 0; + var useEndDate = false; + if ($("#endOptionSelect").val() == "1" && $("#recurrenceEnd").val()) { + endDate = moment($("#recurrenceEnd").val()); + if (!endDate.isValid()) { + return false; + } + useEndDate = true; + } else if ($("#endOptionSelect").val() == "2" && $("#recurrenceCount").val() > 0) { + recurrenceTotal = $("#recurrenceCount").val(); + } else { + return false; // We need either the end date or the number of recurrences to be set or else we can't calculate dates yet + } + + var date = moment($("#startDate").val()); + if (!date.isValid()) { + date = moment(); // Use today's date if there's no start date + } + var originalStart = date.format(); + + var datesPreview = []; + var dates = []; + var frequency = $("#recurrenceFrequencySelect").val(); + var interval = $("#recurrenceInterval").val() || 1; // Assume interval is 1 if not set + + function processMonthlyRepeat() { + tempDate = date.format(); // Keep original date + if (repeatBasedOnDate) { + if (dayNumber <= date.daysInMonth()) { + date.date(dayNumber); + } else { + date.add(1, 'M'); + return false; // Don't generate if the day doesn't exist in the month + } + } else { + endOfMonth = date.endOf("month").format(); + startOfMonth = date.startOf("Month").format(); + if (date.day(weekDay).isBefore(startOfMonth)) { + date.add(1, 'w'); + } + if (weekNumber > 0) { + date.add(weekNumber - 1, 'w'); + } else { // Handle last week of the month + date.add(4, 'w'); + if (date.isAfter(endOfMonth)) { + date.subtract(7, 'd'); + } + } + if (date.isBefore(originalStart)) { + date.add(1, 'M'); // If it's before the start date, add a month and try again + return false; + } + if (date.isAfter(endOfMonth)) { + date = moment(tempDate).add(interval, 'M'); + return false; // Don't generate if the day doesn't exist in the month + } + } + if (!repeatBasedOnDate && offset != 0) { + date.add(offset, 'd'); + if (useEndDate && date.isAfter(endDate)) { + date = moment(tempDate).add(interval, 'M'); + return false; + } + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date = moment(tempDate); + } else { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + } + date.add(interval, 'M'); + return true; + } + + switch (frequency) { + // daily + case '1': + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'd'); + } + } else { + while (count < recurrenceTotal) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'd'); + count++; + } + } + break; + case '2': + // weekly + var days = []; + $("#propertyRowweekDays input:checked").each(function () { + days.push($(this).val()); + }); + if (days.length) { + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + for (i = 0; i < days.length; i++) { + date.day(days[i]); // Set the date to the matching day in the same week + if (date.isBefore(originalStart)) { + date.add(1, 'w'); // If it's before the start date, add a week + } + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + } + date.add(interval, 'w'); + } + } else { + while (count < recurrenceTotal) { + for (i = 0; i < days.length && count < recurrenceTotal; i++) { + date.day(days[i]); // Set the date to the matching day in the same week + if (date.isBefore(originalStart)) { + date.add(1, 'w'); // If it's before the start date, add a week + } + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + count++; + } + date.add(interval, 'w'); + } + } + } else { + return false; //No days selected + } + break; + case '3': + // monthly + var repeatBasedOnDate = $("#monthlyOptionSelect").val() == "2"; + var dayNumber = $("#monthDate").val() || date.format('D'); // If not set, use startDate + var weekNumber = $("#weekNumberSelect").val() || AspenDiscovery.Events.getWeekofMonth(date); + var weekDay = $("#monthDaySelect").val() || date.format('d'); + var endOfMonth; + var startOfMonth; + var tempDate; + var offset = $("#monthOffset").val(); + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + processMonthlyRepeat(); + } + } else { + while (count < recurrenceTotal) { + if (processMonthlyRepeat()) { + count++; // Only count if the date wasn't skipped + } + } + } + break; + + case '4': + // yearly + if (useEndDate) { + while (date.isSameOrBefore(endDate)) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'y'); + } + } else { + while (count < recurrenceTotal) { + datesPreview.push(date.format('dddd, MMMM Do, YYYY')); + dates.push(date.format('YYYY-MM-DD')); + date.add(interval, 'y'); + count++; + } + } + } + $("#datesPreview").html(datesPreview.join("
    ")); + $("#dates").val(dates); + return false; + }, + + collapsePanel: function (panelSelector) { + $(panelSelector + " .panel-title a").removeClass('expanded').addClass('collapsed').attr("aria-expanded", "false"); + $(panelSelector + " .panel").removeClass('active').attr("aria-expanded", "false"); + $(panelSelector + " .accordion_body").removeClass('in').hide(); + $(panelSelector + " .accordion_body").removeClass('in').hide(); + }, + + expandPanel: function (panelSelector) { + $(panelSelector + " .panel-title a").removeClass('collapsed').addClass('expanded').attr("aria-expanded", "true"); + $(panelSelector + " .panel").addClass('active').attr("aria-expanded", "true"); + $(panelSelector + " .accordion_body").addClass('in').show(); + }, + + toggleRecurrenceSections: function (recurrence) { + + function resetRecurrenceSections() { + $("#propertyRowfrequencySection").hide(); + $("#propertyRowweeklySection").hide(); + $("#propertyRowmonthlySection").hide(); + $("#propertyRowrepeatEndsSection").hide(); + $("#propertyRowdatesPreview").hide(); + $("#propertyRowweekDays input").prop("checked", false); + $("#propertyRowweekNumber option").prop("selected", false); + $("#propertyRowmonthDay option").prop("selected", false); + AspenDiscovery.Events.collapsePanel("#accordion_Repeat_Frequency"); + } + var startDate = moment($("#startDate").val()); // Check what happens if invalid date + var dayNumber = startDate.format('d'); + var dayOfWeek = startDate.day(); + var weekOfMonth = AspenDiscovery.Events.getWeekofMonth(startDate); + switch (recurrence) { + case '1': + // Does not repeat + resetRecurrenceSections(); + break; + case '2': + // Daily + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=1]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowdatesPreview").show(); + $("#propertyRowrepeatEndsSection").show(); + break; + case '3': + // Weekly on same day of week + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=2]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + // Show weekly with specific day selected based on startDate + $("#propertyRowweekDays input[value=" + dayOfWeek + "]").prop("checked", true); + $("#propertyRowweeklySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '4': + // Monthly on same day of week + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=3]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + // Show monthly with specific day based on startdate + $("#propertyRowweekNumber option[value=" + weekOfMonth + "]").prop("selected", true); + $("#propertyRowmonthDay option[value=" + dayOfWeek + "]").prop("selected", true); + $("#propertyRowweekNumber").show(); + $("#propertyRowmonthDay").show(); + $("#propertyRowmonthDate").hide(); + $("#propertyRowmonthlySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '5': + // Annually + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=4]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '6': + // Every week day + resetRecurrenceSections(); + $("#recurrenceFrequencySelect option[value=2]").prop("selected","true"); + $("#recurrenceInterval").val("1"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowweekDays input[value!=6][value!=0]").prop("checked", true); + $("#propertyRowweeklySection").show(); + $("#propertyRowrepeatEndsSection").show(); + $("#propertyRowdatesPreview").show(); + break; + case '7': + // Custom - nothing preset + resetRecurrenceSections(); + AspenDiscovery.Events.expandPanel("#accordion_Repeat_Frequency"); + $("#propertyRowfrequencySection").show(); + $("#propertyRowrepeatEndsSection").show(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + }, + + toggleMonthlyOptions: function (option) { + switch (option) { + case '1': + // By day of week + $("#propertyRowweekNumber").show(); + $("#propertyRowmonthDay").show(); + $("#propertyRowmonthDate").hide(); + $("#propertyRowmonthOffset").show(); + break; + case '2': + // By date + $("#propertyRowweekNumber").hide(); + $("#propertyRowmonthDay").hide(); + $("#propertyRowmonthDate").show(); + $("#propertyRowmonthOffset").hide(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + }, + + toggleEndOptions: function (option) { + switch (option) { + case '1': + // By date + $("#propertyRowrecurrenceEnd").show(); + $("#propertyRowrecurrenceCount").hide(); + break; + case '2': + // By count + $("#propertyRowrecurrenceEnd").hide(); + $("#propertyRowrecurrenceCount").show(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; + }, + + toggleSectionsByFrequency: function (option) { + + function resetSections() { + $("#propertyRowweeklySection").hide(); + $("#propertyRowmonthlySection").hide(); + AspenDiscovery.Events.collapsePanel("#propertyRowmonthlySection"); + AspenDiscovery.Events.collapsePanel("#propertyRowweeklySection"); + } + + switch (option) { + case '1': + // Daily + // No extra options + resetSections(); + break; + case '2': + // Weekly + resetSections(); + $("#propertyRowweeklySection").show(); + AspenDiscovery.Events.expandPanel("#propertyRowweeklySection"); + break; + case '3': + // Monthly + resetSections(); + $("#propertyRowmonthlySection").show(); + AspenDiscovery.Events.expandPanel("#propertyRowmonthlySection"); + break; + case '4': + // Annually + // No extra options + resetSections(); + break; + } + AspenDiscovery.Events.calculateRecurrenceDates(); + return false; } }; }(AspenDiscovery.Events || {})); \ No newline at end of file diff --git a/code/web/interface/themes/responsive/js/javascript_files.txt b/code/web/interface/themes/responsive/js/javascript_files.txt index 2b2c74ebd1..bc2d97df06 100644 --- a/code/web/interface/themes/responsive/js/javascript_files.txt +++ b/code/web/interface/themes/responsive/js/javascript_files.txt @@ -35,6 +35,7 @@ aspen/course-reserves.js aspen/dpla.js aspen/ebsco.js aspen/econtent-record.js +aspen/events.js aspen/grouped-work.js aspen/lists.js aspen/collection-spotlights.js diff --git a/code/web/release_notes/25.02.00.MD b/code/web/release_notes/25.02.00.MD index b38ae2ae90..0119108b9f 100644 --- a/code/web/release_notes/25.02.00.MD +++ b/code/web/release_notes/25.02.00.MD @@ -151,6 +151,37 @@ - Reorganized the user agent and IP address usage tracking, so it only runs once the instance is confirmed valid and found. (DIS-257) (*LS*) //katherine +### Aspen Events +- Add permissions for administering Aspen Events. (DIS-192) (*KP*) +- Add interface for creating Aspen Events, event types, event field sets, and event fields. Events have an event type that determines which configurable field set they use. (DIS-192) (*KP*) +- Add options and calculate dates for event recurrence. Events may repeat daily, weekly, monthly, or annually. (DIS-192) (*KP*) +- Generate and allow users to manage event instances for each date an event occurs on. (DIS-192) (*KP*) +- Add Is Valid Event Location option for sublocations. (DIS-192) (*KP*) +- Add indexer and record driver for Aspen Events. (DIS-192) (*KP*) + +
    + +#### New Permissions +- Events > Administer Field Sets +- Events > Administer Event Types +- Events > Administer Events for All Locations +- Events > Administer Events for Home Library Locations +- Events > Administer Events for Home Location +- Events > View Private Events for All Locations +- Events > View Private Events for Home Library Locations +- Events > View Private Events for Home Location +- Events > View Event Reports for All Libraries +- Events > View Event Reports for Home Library +- Events > Print calendars with header images + +#### New Settings +- Events > Aspen Events - Manage Events +- Events > Aspen Events > Event Fields +- Events > Aspen Events > Event Field Sets +- Events > Aspen Events > Event Types +- Events > Aspen Events > Indexing Settings + +
    //kirstien diff --git a/code/web/services/Admin/AJAX.php b/code/web/services/Admin/AJAX.php index 57071fd580..ecfd6ee732 100644 --- a/code/web/services/Admin/AJAX.php +++ b/code/web/services/Admin/AJAX.php @@ -1680,4 +1680,5 @@ public function exportUsageData() { $aspenUsageGraph = new Admin_UsageGraphs(); $aspenUsageGraph->buildCSV(); } + } \ No newline at end of file diff --git a/code/web/services/Events/AJAX.php b/code/web/services/Events/AJAX.php new file mode 100644 index 0000000000..8d03f6b92c --- /dev/null +++ b/code/web/services/Events/AJAX.php @@ -0,0 +1,83 @@ + false, + 'title' => translate([ + 'text' => "Error", + 'isAdminFacing' => true, + ]), + 'message' => translate([ + 'text' => 'Unknown location', + 'isAdminFacing' => true, + ]) + ]; + if (!empty($_REQUEST['locationId'])) { + $eventTypeIds = EventType::getEventTypeIdsForLocation($_REQUEST['locationId']); + if (!empty($eventTypeIds)) { + $result = [ + 'success' => true, + 'eventTypeIds' => json_encode($eventTypeIds), + ]; + } else { + $result = [ + 'success' => true, + 'eventTypeIds' => '', + 'title' => translate([ + 'text' => "No available event types", + 'isAdminFacing' => true, + ]), + 'message' => translate([ + 'text' => 'No event types are available for this location.', + 'isAdminFacing' => true, + ]) + ]; + } + } + return $result; + } + + /** @noinspection PhpUnused */ + public function getEventTypeFields() { + require_once ROOT_DIR . '/sys/Events/EventType.php'; + $result = [ + 'success' => false, + 'title' => translate([ + 'text' => "Error", + 'isAdminFacing' => true, + ]), + 'message' => translate([ + 'text' => 'Unknown event type.', + 'isAdminFacing' => true, + ]) + ]; + if (!empty($_REQUEST['eventTypeId'])) { + $eventType = new EventType(); + $eventType->id = $_REQUEST['eventTypeId']; + if ($eventType->find(true)) { + $fieldStructure = $eventType->getFieldSetFields(); + global $interface; + $fieldHTML = []; + foreach ($fieldStructure as $property) { + $interface->assign('property', $property); + $fieldHTML[] = $interface->fetch('DataObjectUtil/property.tpl'); + } + $locations = $eventType->getLocations(); + $result = [ + 'success' => true, + 'eventType' => $eventType->jsonSerialize(), + 'typeFields' => $fieldHTML, + 'locationIds' => json_encode(array_keys($locations)), + ]; + } + } + return $result; + } + +} \ No newline at end of file diff --git a/code/web/services/Events/EventFieldSets.php b/code/web/services/Events/EventFieldSets.php new file mode 100644 index 0000000000..67ff2c7d23 --- /dev/null +++ b/code/web/services/Events/EventFieldSets.php @@ -0,0 +1,80 @@ +orderBy($this->getSort()); + $this->applyFilters($object); + $object->limit(($page - 1) * $recordsPerPage, $recordsPerPage); + $list = []; + $object->find(); + while ($object->fetch()) { + $list[$object->id] = clone $object; + } + return $list; + } + + function getDefaultSort(): string { + return 'name asc'; + } + + function getObjectStructure($context = ''): array { + return EventFieldSet::getObjectStructure($context); + } + + function getPrimaryKeyColumn(): string { + return 'id'; + } + + function getIdKeyColumn(): string { + return 'id'; + } + + function getInstructions(): string { + return 'https://help.aspendiscovery.org/help/catalog/events'; + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + $breadcrumbs[] = new Breadcrumb('/Admin/Home', 'Administration Home'); + $breadcrumbs[] = new Breadcrumb('/Admin/Home#events', 'Events'); + $breadcrumbs[] = new Breadcrumb('/Events/EventsFieldSets', 'Events Field Sets'); + return $breadcrumbs; + } + + function getActiveAdminSection(): string { + return 'events'; + } + + function canView(): bool { + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + return UserAccount::userHasPermission(['Administer Field Sets']); + } + return false; + } + + function canBatchEdit(): bool { + return UserAccount::userHasPermission(['Administer Field Sets']); + } +} \ No newline at end of file diff --git a/code/web/services/Events/EventFields.php b/code/web/services/Events/EventFields.php new file mode 100644 index 0000000000..b95bc75859 --- /dev/null +++ b/code/web/services/Events/EventFields.php @@ -0,0 +1,79 @@ +orderBy($this->getSort()); + $this->applyFilters($object); + $object->limit(($page - 1) * $recordsPerPage, $recordsPerPage); + $list = []; + $object->find(); + while ($object->fetch()) { + $list[$object->id] = clone $object; + } + return $list; + } + + function getDefaultSort(): string { + return 'name asc'; + } + + function getObjectStructure($context = ''): array { + return EventField::getObjectStructure($context); + } + + function getPrimaryKeyColumn(): string { + return 'id'; + } + + function getIdKeyColumn(): string { + return 'id'; + } + + function getInstructions(): string { + return 'https://help.aspendiscovery.org/help/catalog/events'; + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + $breadcrumbs[] = new Breadcrumb('/Admin/Home', 'Administration Home'); + $breadcrumbs[] = new Breadcrumb('/Admin/Home#events', 'Events'); + $breadcrumbs[] = new Breadcrumb('/Events/EventsFields', 'Events Fields'); + return $breadcrumbs; + } + + function getActiveAdminSection(): string { + return 'events'; + } + + function canView(): bool { + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + return UserAccount::userHasPermission(['Administer Field Sets']); + } + return false; + } + + function canBatchEdit(): bool { + return UserAccount::userHasPermission(['Administer Field Sets']); + } +} \ No newline at end of file diff --git a/code/web/services/Events/EventInstances.php b/code/web/services/Events/EventInstances.php new file mode 100644 index 0000000000..a5f40d9cac --- /dev/null +++ b/code/web/services/Events/EventInstances.php @@ -0,0 +1,86 @@ +orderBy($this->getSort()); + $this->applyFilters($object); + $object->limit(($page - 1) * $recordsPerPage, $recordsPerPage); + $list = []; + $object->find(); + while ($object->fetch()) { + $list[$object->id] = clone $object; + } + return $list; + } + + function getDefaultSort(): string { + return 'startDate asc'; + } + + function getObjectStructure($context = ''): array { + return EventInstanceGroup::getObjectStructure($context); + } + + function getPrimaryKeyColumn(): string { + return 'id'; + } + + function getIdKeyColumn(): string { + return 'id'; + } + + function getInstructions(): string { + return 'https://help.aspendiscovery.org/help/catalog/events'; + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + $breadcrumbs[] = new Breadcrumb('/Admin/Home', 'Administration Home'); + $breadcrumbs[] = new Breadcrumb('/Admin/Home#events', 'Events'); + $breadcrumbs[] = new Breadcrumb('/Events/EventInstances', 'Event Instances'); + return $breadcrumbs; + } + + function getActiveAdminSection(): string { + return 'events'; + } + + function canView(): bool { + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + return UserAccount::userHasPermission(['Administer Events for All Locations']); + } + return false; + } + + function canBatchEdit(): bool { + return UserAccount::userHasPermission(['Administer Events for All Locations']); + } + + public function hasMultiStepAddNew(): bool { + return true; + } +} \ No newline at end of file diff --git a/code/web/services/Events/EventTypes.php b/code/web/services/Events/EventTypes.php new file mode 100644 index 0000000000..4ce2666133 --- /dev/null +++ b/code/web/services/Events/EventTypes.php @@ -0,0 +1,79 @@ +orderBy($this->getSort()); + $this->applyFilters($object); + $object->limit(($page - 1) * $recordsPerPage, $recordsPerPage); + $list = []; + $object->find(); + while ($object->fetch()) { + $list[$object->id] = clone $object; + } + return $list; + } + + function getDefaultSort(): string { + return 'title asc'; + } + + function getObjectStructure($context = ''): array { + return EventType::getObjectStructure($context); + } + + function getPrimaryKeyColumn(): string { + return 'id'; + } + + function getIdKeyColumn(): string { + return 'id'; + } + + function getInstructions(): string { + return 'https://help.aspendiscovery.org/help/catalog/events'; + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + $breadcrumbs[] = new Breadcrumb('/Admin/Home', 'Administration Home'); + $breadcrumbs[] = new Breadcrumb('/Admin/Home#events', 'Events'); + $breadcrumbs[] = new Breadcrumb('/Events/EventTypes', 'Events Types'); + return $breadcrumbs; + } + + function getActiveAdminSection(): string { + return 'events'; + } + + function canView(): bool { + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + return UserAccount::userHasPermission(['Administer Event Types']); + } + return false; + } + + function canBatchEdit(): bool { + return UserAccount::userHasPermission(['Administer Event Types']); + } +} \ No newline at end of file diff --git a/code/web/services/Events/Events.php b/code/web/services/Events/Events.php new file mode 100644 index 0000000000..8d7dff0064 --- /dev/null +++ b/code/web/services/Events/Events.php @@ -0,0 +1,83 @@ +orderBy($this->getSort()); + $this->applyFilters($object); + $object->limit(($page - 1) * $recordsPerPage, $recordsPerPage); + $list = []; + $object->find(); + while ($object->fetch()) { + $list[$object->id] = clone $object; + } + return $list; + } + + function getDefaultSort(): string { + return 'startDate asc'; + } + + function getObjectStructure($context = ''): array { + return Event::getObjectStructure($context); + } + + function getPrimaryKeyColumn(): string { + return 'id'; + } + + function getIdKeyColumn(): string { + return 'id'; + } + + function getInstructions(): string { + return 'https://help.aspendiscovery.org/help/catalog/events'; + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + $breadcrumbs[] = new Breadcrumb('/Admin/Home', 'Administration Home'); + $breadcrumbs[] = new Breadcrumb('/Admin/Home#events', 'Events'); + $breadcrumbs[] = new Breadcrumb('/Events/Events', 'Events'); + return $breadcrumbs; + } + + function getActiveAdminSection(): string { + return 'events'; + } + + function canView(): bool { + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + return UserAccount::userHasPermission(['Administer Events for All Locations']); + } + return false; + } + + function canBatchEdit(): bool { + return UserAccount::userHasPermission(['Administer Events for All Locations']); + } + + public function hasMultiStepAddNew() : bool { + return true; + } +} \ No newline at end of file diff --git a/code/web/services/Events/IndexingSettings.php b/code/web/services/Events/IndexingSettings.php new file mode 100644 index 0000000000..bf29e557db --- /dev/null +++ b/code/web/services/Events/IndexingSettings.php @@ -0,0 +1,83 @@ +limit(($page - 1) * $recordsPerPage, $recordsPerPage); + $this->applyFilters($object); + $object->orderBy($this->getSort()); + $object->find(); + $objectList = []; + while ($object->fetch()) { + $objectList[$object->id] = clone $object; + } + return $objectList; + } + + function getDefaultSort(): string { + return 'id asc'; + } + + function getObjectStructure($context = ''): array { + return EventsIndexingSetting::getObjectStructure($context); + } + + function getPrimaryKeyColumn(): string { + return 'id'; + } + + function getIdKeyColumn(): string { + return 'id'; + } + + function getAdditionalObjectActions($existingObject): array { + return []; + } + + function getInstructions(): string { + return ''; + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + $breadcrumbs[] = new Breadcrumb('/Admin/Home', 'Administration Home'); + $breadcrumbs[] = new Breadcrumb('/Admin/Home#events', 'Events'); + $breadcrumbs[] = new Breadcrumb('/Events/IndexingSettings', 'Indexing Settings'); + return $breadcrumbs; + } + + function getActiveAdminSection(): string { + return 'events'; + } + + function canView(): bool { + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + return UserAccount::userHasPermission(['Administer Events for All Locations']); + } + return false; + } + + function canBatchEdit(): bool { + return UserAccount::userHasPermission(['Administer Events for All Locations']); + } +} \ No newline at end of file diff --git a/code/web/services/NativeEvents/event.php b/code/web/services/NativeEvents/event.php new file mode 100644 index 0000000000..af407355ce --- /dev/null +++ b/code/web/services/NativeEvents/event.php @@ -0,0 +1,39 @@ +recordDriver = new NativeEventRecordDriver($id); + if (!$this->recordDriver->isValid()) { + global $interface; + $interface->assign('module', 'Error'); + $interface->assign('action', 'Handle404'); + require_once ROOT_DIR . "/services/Error/Handle404.php"; + $actionClass = new Error_Handle404(); + $actionClass->launch(); + die(); + } + $interface->assign('recordDriver', $this->recordDriver); + $interface->assign('eventsInLists', true); + $interface->assign('isStaff', UserAccount::isStaff()); + + // Display Page + $this->display('event.tpl', $this->recordDriver->getTitle(), null, false); + } + + function getBreadcrumbs(): array { + $breadcrumbs = []; + if (!empty($this->lastSearch)) { + $breadcrumbs[] = new Breadcrumb($this->lastSearch, 'Event Search Results'); + } + $breadcrumbs[] = new Breadcrumb('', $this->recordDriver->getTitle()); + return $breadcrumbs; + } +} \ No newline at end of file diff --git a/code/web/sys/Account/User.php b/code/web/sys/Account/User.php index 5722a9077e..a7766d707a 100644 --- a/code/web/sys/Account/User.php +++ b/code/web/sys/Account/User.php @@ -4545,6 +4545,15 @@ public function getAdminActions() { if (array_key_exists('Events', $enabledModules)) { $sections['events'] = new AdminSection('Events'); + if (SystemVariables::getSystemVariables()->enableAspenEvents) { + $aspenEventsAction = new AdminAction('Aspen Events - Manage Events', 'Add and manage Aspen Events.', '/Events/Events'); + if ($sections['events']->addAction($aspenEventsAction, 'Administer Events for All Locations')) { + $aspenEventsAction->addSubAction(new AdminAction('Configure Event Fields', 'Define event fields for Aspen Events.', '/Events/EventFields'), 'Administer Field Sets'); + $aspenEventsAction->addSubAction(new AdminAction('Configure Event Field Sets', 'Define sets of event fields to use for Aspen Events.', '/Events/EventFieldSets'), 'Administer Field Sets'); + $aspenEventsAction->addSubAction(new AdminAction('Configure Event Types', 'Define event types to use for Aspen Events.', '/Events/EventTypes'), 'Administer Event Types'); + $aspenEventsAction->addSubAction(new AdminAction('Indexing Settings', 'Aspen Events Indexing Settings.', '/Events/IndexingSettings'), 'Administer Events for All Locations'); + } + } $sections['events']->addAction(new AdminAction('Assabet - Interactive Settings', 'Define collections to be loaded into Aspen Discovery.', '/Events/AssabetSettings'), 'Administer Assabet Settings'); $sections['events']->addAction(new AdminAction('Communico - Attend Settings', 'Define collections to be loaded into Aspen Discovery.', '/Events/CommunicoSettings'), 'Administer Communico Settings'); $sections['events']->addAction(new AdminAction('Library Market - Calendar Settings', 'Define collections to be loaded into Aspen Discovery.', '/Events/LMLibraryCalendarSettings'), 'Administer LibraryMarket LibraryCalendar Settings'); diff --git a/code/web/sys/Covers/BookCoverProcessor.php b/code/web/sys/Covers/BookCoverProcessor.php index f7e35cdc65..0b51421da6 100644 --- a/code/web/sys/Covers/BookCoverProcessor.php +++ b/code/web/sys/Covers/BookCoverProcessor.php @@ -82,6 +82,14 @@ public function loadCover($configArray, $timer, $logger) { if ($this->getAssabetCover($this->id)){ return true; } + } elseif ($this->type == 'nativeEvent_event') { + if ($this->getNativeEventsDateCover($this->id)){ + return true; + } + } elseif ($this->type == 'nativeEvent_eventRecord') { + if ($this->getNativeEventsImageCover($this->id)){ + return true; + } } elseif ($this->type == 'webpage' || $this->type == 'WebPage' || $this->type == 'BasicPage' || $this->type == 'WebResource' || $this->type == 'PortalPage' || $this->type == 'GrapesPage') { if ($this->getWebPageCover($this->id)) { return true; @@ -1760,6 +1768,73 @@ private function getAssabetCover($id) { return false; } + private function getNativeEventsDateCover($id) { + if (strpos($id, ':') !== false) { + [ + , + $id, + ] = explode(":", $id); + } + require_once ROOT_DIR . '/RecordDrivers/NativeEventRecordDriver.php'; + $driver = new NativeEventRecordDriver($id); + if (!($driver->isValid())){ //if driver isn't valid, likely a past event on a list + require_once ROOT_DIR . '/sys/Covers/EventCoverBuilder.php'; + require_once ROOT_DIR . '/sys/Events/UserEventsEntry.php'; + $coverBuilder = new EventCoverBuilder(); + $userEntry = new UserEventsEntry(); + $userEntry->sourceId = $id; + if ($userEntry->find(true)){ + $startDate = new DateTime("@$userEntry->eventDate"); + $startDate->setTimezone(new DateTimeZone(date_default_timezone_get())); + $props = [ + 'eventDate' => $startDate, + 'isPastEvent' => true, + ]; + $title = $userEntry->title; + } else{ + $props = [ + 'eventDate' => $driver->getStartDateFromDB($id), + 'isPastEvent' => true, + ]; + $title = $driver->getTitleFromDB($id); + } + $coverBuilder->getCover($title, $this->cacheFile, $props); + return $this->processImageURL('default_event', $this->cacheFile, false); + } else if ($driver) { + require_once ROOT_DIR . '/sys/Covers/EventCoverBuilder.php'; + $coverBuilder = new EventCoverBuilder(); + $isPast = false; + if (array_key_exists('isPast', $_REQUEST)){ + $isPast = $_REQUEST['isPast']; + } + $props = [ + 'eventDate' => $driver->getStartDate(), + 'isPastEvent' => $isPast, + ]; + $coverBuilder->getCover($driver->getTitle(), $this->cacheFile, $props); + return $this->processImageURL('default_event', $this->cacheFile, false); + } + return false; + } + + private function getNativeEventsImageCover($id) { + if (strpos($id, ':') !== false) { + [ + , + $id, + ] = explode(":", $id); + } + require_once ROOT_DIR . '/RecordDrivers/NativeEventRecordDriver.php'; + $driver = new NativeEventRecordDriver($id); + if (($driver->isValid()) && $driver->getCoverImagePath()) { + $uploadedImage = $driver->getCoverImagePath(); + if (file_exists($uploadedImage)) { + return $this->processImageURL('upload', $uploadedImage); + } + } + return false; + } + private function getWebPageCover($id) { //Build a cover based on the title of the page require_once ROOT_DIR . '/sys/Covers/WebPageCoverBuilder.php'; diff --git a/code/web/sys/DBMaintenance/version_updates/25.02.00.php b/code/web/sys/DBMaintenance/version_updates/25.02.00.php index 165904e8cb..fa72826eac 100644 --- a/code/web/sys/DBMaintenance/version_updates/25.02.00.php +++ b/code/web/sys/DBMaintenance/version_updates/25.02.00.php @@ -132,6 +132,140 @@ function getUpdates25_02_00(): array { ], //katherine + 'native_events_permissions' => [ + 'title' => 'Native Events Permissions', + 'description' => 'Add new permissions for native events', + 'continueOnError' => true, + 'sql' => [ + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'Administer Field Sets', 'Events', 30, 'Allows the user to administer field sets for native events.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'Administer Event Types', 'Events', 40, 'Allows the user to administer native event types.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'Administer Events for All Locations', 'Events', 50, 'Allows the user to administer native events for all locations.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'Administer Events for Home Library Locations', 'Events', 51, 'Allows the user to administer native events for home library locations.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'Administer Events for Home Location', 'Events', 52, 'Allows the user to administer native events for home location.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'View Private Events for All Locations', 'Events', 60, 'Allows the user to view private native events for all locations.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'View Private Events for Home Library Locations', 'Events', 61, 'Allows the user to view private native events for home library locations.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'View Private Events for Home Location', 'Events', 62, 'Allows the user to view private native events for home location.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'View Event Reports for All Libraries', 'Events', 70, 'Allows the user to view event reports for all libraries.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'View Event Reports for Home Library', 'Events', 71, 'Allows the user to view event reports for their home library.')", + "INSERT INTO permissions (sectionName, name, requiredModule, weight, description) VALUES ('Events', 'Print Calendars with Header Images', 'Events', 80, 'Allows the user to print calendars with header images.')", + "INSERT INTO role_permissions(roleId, permissionId) VALUES ((SELECT roleId from roles where name='opacAdmin'), (SELECT id from permissions where name='Administer Field Sets'))", + "INSERT INTO role_permissions(roleId, permissionId) VALUES ((SELECT roleId from roles where name='opacAdmin'), (SELECT id from permissions where name='Administer Event Types'))", + "INSERT INTO role_permissions(roleId, permissionId) VALUES ((SELECT roleId from roles where name='opacAdmin'), (SELECT id from permissions where name='Administer Events for All Locations'))", + "INSERT INTO role_permissions(roleId, permissionId) VALUES ((SELECT roleId from roles where name='opacAdmin'), (SELECT id from permissions where name='View Private Events for All Locations'))", + "INSERT INTO role_permissions(roleId, permissionId) VALUES ((SELECT roleId from roles where name='opacAdmin'), (SELECT id from permissions where name='View Event Reports for All Libraries'))", + "INSERT INTO role_permissions(roleId, permissionId) VALUES ((SELECT roleId from roles where name='opacAdmin'), (SELECT id from permissions where name='Print Calendars with Header Images'))", + ], + ], //native_events_permissions + 'native_event_tables' => [ + 'title' => 'Native Event Tables', + 'description' => 'Add new tables for native events', + 'continueOnError' => true, + 'sql' => [ + "CREATE TABLE event_field ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE, + description VARCHAR(100) NOT NULL, + type TINYINT NOT NULL DEFAULT 0, + allowableValues VARCHAR(150), + defaultValue VARCHAR(150), + facetName INT NOT NULL DEFAULT 0 + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_field_set ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_field_set_field ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventFieldId INT NOT NULL, + eventFieldSetId INT NOT NULL + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_type ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventFieldSetId INT NOT NULL, + title VARCHAR(50) NOT NULL UNIQUE, + titleCustomizable TINYINT(1) NOT NULL DEFAULT 1, + description VARCHAR(100) NOT NULL, + descriptionCustomizable TINYINT(1) NOT NULL DEFAULT 1, + cover VARCHAR(100) DEFAULT NULL, + coverCustomizable TINYINT(1) NOT NULL DEFAULT 1, + eventLength FLOAT NOT NULL DEFAULT 1, + lengthCustomizable TINYINT(1) NOT NULL DEFAULT 1, + archived TINYINT(1) NOT NULL DEFAULT 0 + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_type_library ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventTypeId INT NOT NULL, + libraryId INT NOT NULL + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_type_location ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventTypeId INT NOT NULL, + locationId INT NOT NULL + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventTypeId INT NOT NULL, + locationId INT NOT NULL, + sublocationId INT, + title VARCHAR(50), + description VARCHAR(500), + cover VARCHAR(100) DEFAULT NULL, + private TINYINT(1) NOT NULL DEFAULT 1, + startDate DATE NOT NULL DEFAULT (CURRENT_DATE), + startTime TIME NOT NULL DEFAULT (CURRENT_TIME), + eventLength INT NOT NULL DEFAULT 60, + recurrenceOption TINYINT, + recurrenceFrequency TINYINT, + recurrenceInterval INT NOT NULL DEFAULT 1, + weekDays VARCHAR(25), + monthlyOption TINYINT, + monthDay TINYINT, + monthDate TINYINT, + monthOffset TINYINT, + endOption TINYINT, + recurrenceEnd DATE, + recurrenceCount INT + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_event_field ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventId INT NOT NULL, + eventFieldId INT NOT NULL, + value VARCHAR(150) NOT NULL + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "CREATE TABLE event_instance ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + eventId INT NOT NULL, + date DATE NOT NULL, + time TIME NOT NULL, + length INT NOT NULL, + status TINYINT(1) NOT NULL DEFAULT 1, + note VARCHAR(150) + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + "ALTER TABLE sublocation ADD COLUMN isValidEventLocation TINYINT(1) DEFAULT 0", + ] + ], //native_events_tables + 'native_events_indexing_tables' => [ + 'title' => 'Native Events Indexing Tables', + 'description' => 'Add new tables for native events related to indexing', + 'continueOnError' => true, + 'sql' => [ + "CREATE TABLE events_indexing_settings ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + numberOfDaysToIndex INT DEFAULT 365, + runFullUpdate TINYINT(1) DEFAULT 0, + lastUpdateOfAllEvents INT, + lastUpdateOfChangedEvents INT + ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci", + ] + ], //native_events_indexing_tables + 'native_events_system_variable' => [ + 'title' => 'Native Events System Variable', + 'description' => 'Add system variable to turn on native events', + 'continueOnError' => true, + 'sql' => [ + "ALTER TABLE system_variables ADD COLUMN enableAspenEvents TINYINT(1) DEFAULT 0" + ] + ], //native_events_indexing_tables //kirstien - Grove 'lida_general_settings_add_more_info' => [ @@ -178,4 +312,4 @@ function getUpdates25_02_00(): array { //other ]; -} +} \ No newline at end of file diff --git a/code/web/sys/DataObjectUtil.php b/code/web/sys/DataObjectUtil.php index 6d12582f8f..5ea3bbc6e7 100644 --- a/code/web/sys/DataObjectUtil.php +++ b/code/web/sys/DataObjectUtil.php @@ -308,6 +308,15 @@ static function processProperty(DataObject $object, $property, $fieldLocks) { $object->setProperty($propertyName, $time, $property); } + } elseif ($property['type'] == 'time') { + if (empty(strlen($_REQUEST[$propertyName])) || strlen($_REQUEST[$propertyName]) == 0 || $_REQUEST[$propertyName] == '00:00:00') { + $object->setProperty($propertyName, null, $property); + } else { + $dateParts = date_parse($_REQUEST[$propertyName]); + $time = $dateParts['hour'] . ':' . $dateParts['minute'] . ':' . $dateParts['second']; + $object->setProperty($propertyName, $time, $property); + } + } elseif ($property['type'] == 'dayMonth') { if (isset($_REQUEST[$propertyName . "_month"]) && isset($_REQUEST[$propertyName . "_day"])){ if (is_numeric($_REQUEST[$propertyName . "_month"]) && is_numeric($_REQUEST[$propertyName . "_day"])) { diff --git a/code/web/sys/Events/Event.php b/code/web/sys/Events/Event.php new file mode 100644 index 0000000000..2803b92f57 --- /dev/null +++ b/code/web/sys/Events/Event.php @@ -0,0 +1,951 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id', + ], + 'locationId' => [ + 'property' => 'locationId', + 'type' => 'enum', + 'label' => 'Location', + 'description' => 'Location of the event', + 'required' => true, + 'values' => $locationList, + 'onchange' => 'return AspenDiscovery.Events.getEventTypesForLocation(this.value);', + ], + 'eventTypeId' => [ + 'property' => 'eventTypeId', + 'type' => 'enum', + 'label' => 'Event Type', + 'description' => 'The type of event', + 'required' => true, + 'values' => $eventTypes, + 'onchange' => "return AspenDiscovery.Events.getEventTypeFields(this.value);" + ], + 'title' => [ + 'property' => 'title', + 'type' => 'text', + 'label' => 'Title', + 'description' => 'The title for this event', + ], + 'infoSection' => [ + 'property' => 'infoSection', + 'type' => 'section', + 'label' => 'Event Information', + 'expandByDefault' => true, + 'properties' => [ + 'description' => [ + 'property' => 'description', + 'type' => 'textarea', + 'label' => 'Description', + 'description' => 'The description for this event', + ], + 'cover' => [ + 'property' => 'cover', + 'type' => 'image', + 'label' => 'Cover', + 'maxWidth' => 280, + 'maxHeight' => 280, + 'description' => 'The cover for this event', + 'path' => "$coverPath/aspenEvents/", + 'hideInLists' => true, + ], + 'fieldSetFieldSection' => [ + 'property' => 'fieldSetFieldSection', + 'type' => 'section', + 'label' => 'Fields for this Event Type', + 'hideInLists' => true, + 'expandByDefault' => true, + 'properties' => [], + ], + 'private' => [ + 'property' => 'private', + 'type' => 'checkbox', + 'label' => 'Private?', + 'default' => false, + 'description' => 'Private events are limited to those with permission to view private events', + ], + ], + ], + 'startDateForList' => [ + 'property' => 'startDateForList', + 'type' => 'date', + 'label' => 'Start Date', + 'readOnly' => true, + 'hiddenByDefault' => true, + ], + 'scheduleSection' => [ + 'property' => 'scheduleSection', + 'type' => 'section', + 'label' => 'Event Scheduling', + 'expandByDefault' => true, + 'hiddenByDefault' => true, + 'properties' => [ + 'startDate' => [ + 'property' => 'startDate', + 'type' => 'hidden', + ], + 'startTime' => [ + 'property' => 'startTime', + 'type' => 'hidden', + ], + 'eventLength' => [ + 'property' => 'eventLength', + 'type' => 'hidden', + ], + 'endDate' => [ + 'property' => 'endDate', + 'type' => 'hidden', + ], + 'endTime' => [ + 'property' => 'endTime', + 'type' => 'hidden', + ], + 'recurrenceOption' => [ + 'property' => 'recurrenceOption', + 'type' => 'hidden', + 'default' => 1, + ], + 'frequencySection' => [ + 'property' => 'frequencySection', + 'type' => 'section', + 'hiddenByDefault' => true, + 'properties' => [ + 'recurrenceInterval' => [ + 'property' => 'recurrenceInterval', + 'type' => 'hidden', + 'default' => 1, + ], + 'recurrenceFrequency' => [ + 'property' => 'recurrenceFrequency', + 'type' => 'hidden', + 'default' => 1, + ] + ] + ], + 'weeklySection' => [ + 'property' => 'weeklySection', + 'type' => 'section', + 'hiddenByDefault' => true, + 'properties' => [ + 'weekDays' => [ + 'property' => 'weekDays', + 'type' => 'hidden', + ], + ] + ], + 'monthlySection' => [ + 'property' => 'monthlySection', + 'type' => 'section', + 'hiddenByDefault' => true, + 'properties' => [ + 'monthlyOption' => [ + 'property' => 'monthlyOption', + 'type' => 'hidden', + 'default' => 1 + ], + 'monthDay' => [ + 'property' => 'monthDay', + 'type' => 'hidden', + 'default' => 1, + ], + 'monthDate' => [ + 'property' => 'monthDate', + 'type' => 'hidden', + ], + 'monthOffset' => [ + 'property' => 'monthOffset', + 'type' => 'hidden', + ], + ] + ], + 'repeatEndsSection' => [ + 'property' => 'repeatEndsSection', + 'type' => 'section', + 'hiddenByDefault' => true, + 'properties' => [ + 'endOption' => [ + 'property' => 'endOption', + 'type' => 'hidden', + 'default' => 1, + ], + 'recurrenceEnd' => [ + 'property' => 'recurrenceEnd', + 'type' => 'hidden', + ], + 'recurrenceCount' => [ + 'property' => 'recurrenceCount', + 'type' => 'hidden', + 'default' => 1, + ], + ], + ], + 'datesPreview' => [ + 'property' => 'datesPreview', + 'type' => 'hidden', + ], + ], + ], + 'dates' => [ + 'property' => 'dates', + 'type' => 'hidden', + 'hideInLists' => true, + ], + 'instanceCount' => [ + 'property' => 'instanceCount', + 'type' => 'integer', + 'label' => 'Total Upcoming Events', + 'hiddenByDefault' => true, + 'readOnly' => true, + ], + ]; + // Add empty, hidden, readonly copies of all potential fields so that data can be added if they exist for any selected event type + $eventFieldList = EventField::getEventFieldList(); + foreach ($eventFieldList as $fieldId => $field) { + $structure['infoSection']['properties']['fieldSetFieldSection']['properties'][$fieldId] = [ + 'property' => $fieldId, + 'label' => $field, + 'readOnly' => true, + 'type' => 'hidden', + ]; + } + if ($context == 'addNew') { + $structure['eventTypeId'] = [ + 'property' => 'eventTypeId', + 'type' => 'enum', + 'label' => 'Event Type', + 'required' => true, + 'description' => 'The type of event', + 'placeholder' => 'Choose an event type', + 'values' => $eventTypes, + 'onchange' => "return AspenDiscovery.Events.getEventTypeFields(this.value);", + ]; + $structure['title']['hiddenByDefault'] = true; + $structure['infoSection']['hiddenByDefault'] = true; + $structure['infoSection']['properties']['description']['hiddenByDefault'] = true; + $structure['infoSection']['properties']['cover']['hiddenByDefault'] = true; + $structure['infoSection']['properties']['fieldSetFieldSection']['hiddenByDefault'] = true; + } else { + $structure['eventTypeId'] = [ + 'property' => 'eventTypeId', + 'type' => 'enum', + 'label' => 'Event Type', + 'description' => 'The type of event', + 'values' => $eventTypes, + 'readOnly' => true, + ]; + $structure['locationId']['readOnly'] = true; + $structure['scheduleSection']['properties'] = [ + 'startDate' => [ + 'property' => 'startDate', + 'type' => 'date', + 'label' => 'Event Date', + 'description' => 'The date this event starts', + 'onchange' => "return AspenDiscovery.Events.updateRecurrenceOptions(this.value);" + ], + 'startTime' => [ + 'property' => 'startTime', + 'type' => 'time', + 'label' => 'Start Time', + 'description' => 'The time this event starts', + 'onchange' => 'return AspenDiscovery.Events.calculateEndTime();' + ], + 'eventLength' => [ + 'property' => 'eventLength', + 'type' => 'integer', + 'label' => 'Event Length (Hours)', + 'description' => 'How long this event lasts', + 'note' => 'Default determined by Event Type', + 'onchange' => "return AspenDiscovery.Events.calculateEndTime();" + ], + 'endDate' => [ + 'property' => 'endDate', + 'type' => 'date', + 'label' => 'End Date', + 'description' => 'The date this event ends', + 'note' => 'Automatically calculated based on Event Date, Start Time, and Event Length', + ], + 'endTime' => [ + 'property' => 'endTime', + 'type' => 'time', + 'label' => 'End Time', + 'description' => 'The time this event ends', + 'note' => 'Automatically calculated based on Event Date, Start Time, and Event Length', + ], + 'recurrenceOption' => [ + 'property' => 'recurrenceOption', + 'type' => 'enum', + 'label' => 'Repeat Options', + 'description' => 'How this event repeats', + 'values' => [ + '1' => 'Does not repeat', + '2' => 'Daily', + '3' => 'Weekly on this day', // Update option descriptions based on the start date + '4' => 'Monthly on the same weekday', + '5' => 'Annually on the same date', + '6' => 'Every weekday (Monday - Friday)', + '7' => 'Custom', + ], + 'onchange' => "return AspenDiscovery.Events.toggleRecurrenceSections(this.value);" + ], + 'frequencySection' => [ + 'property' => 'frequencySection', + 'type' => 'section', + 'label' => 'Repeat Frequency', + 'properties' => [ + 'recurrenceFrequency' => [ + 'property' => 'recurrenceFrequency', + 'type' => 'enum', + 'label' => 'Repeat Frequency', + 'values' => [ + '1' => 'Daily', + '2' => 'Weekly', + '3' => 'Monthly', + '4' => 'Annually on the same date', + ], + 'onchange' => "return AspenDiscovery.Events.toggleSectionsByFrequency(this.value);" + ], + 'recurrenceInterval' => [ + 'property' => 'recurrenceInterval', + 'type' => 'integer', + 'label' => 'Repeat Interval', + 'note' => 'Repeats every [interval] days/weeks/months', + ], + ], + 'expandByDefault' => false, + 'hiddenByDefault' => true, + ], + 'weeklySection' => [ + 'property' => 'weeklySection', + 'type' => 'section', + 'label' => 'Repeat Based on Week', + 'properties' => [ + 'weekDays' => [ + 'property' => 'weekDays', + 'type' => 'multiSelect', + 'listStyle' => 'checkbox', + 'label' => 'Day(s) to Repeat On', + 'values' => [ + '0' => 'Sunday', + '1' => 'Monday', + '2' => 'Tuesday', + '3' => 'Wednesday', + '4' => 'Thursday', + '5' => 'Friday', + '6' => 'Saturday', + ], + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();", + ], + ], + 'hiddenByDefault' => true, + 'expandByDefault' => false, + ], + 'monthlySection' => [ + 'property' => 'monthlySection', + 'type' => 'section', + 'label' => 'Repeat Based on Month', + 'properties' => [ + 'monthlyOption' => [ + 'property' => 'monthlyOption', + 'type' => 'enum', + 'label' => 'Monthly Repeat Options', + 'values' => [ + '1' => 'Repeat based on day of the week', + '2' => 'Repeat based on date', + ], + 'onchange' => "return AspenDiscovery.Events.toggleMonthlyOptions(this.value);" + ], + 'weekNumber' => [ + 'property' => 'weekNumber', + 'type' => 'enum', + 'label' => 'Week Number', + 'values' => [ + '1' => '1st', + '2' => '2nd', + '3' => '3rd', + '4' => '4th', + '5' => '5th', + '-1' => 'Last', + ], + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();", + ], + 'monthDay' => [ + 'property' => 'monthDay', + 'type' => 'enum', + 'label' => 'Day to Repeat On', + 'values' => [ + '0' => 'Sunday', + '1' => 'Monday', + '2' => 'Tuesday', + '3' => 'Wednesday', + '4' => 'Thursday', + '5' => 'Friday', + '6' => 'Saturday', + ], + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();", + ], + 'monthDate' => [ + 'property' => 'monthDate', + 'type' => 'integer', + 'label' => 'Date to repeat on', + 'hiddenByDefault' => true, + 'min' => '-1', + 'max' => '31', + 'note' => 'Use -1 to repeat on the last day of the month', + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();", + ], + 'monthOffset' => [ + 'property' => 'monthOffset', + 'type' => 'integer', + 'label' => 'Offset', + 'default' => '0', + 'note' => 'Number of days to add before or after (use negative numbers for before)', + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();", + ] + ], + 'hiddenByDefault' => true, + 'expandByDefault' => false, + ], + 'repeatEndsSection' => [ + 'property' => 'repeatEndsSection', + 'type' => 'section', + 'label' => 'Repeat Ends', + 'hiddenByDefault' => true, + 'expandByDefault' => true, + 'properties' => [ + 'endOption' => [ + 'property' => 'endOption', + 'type' => 'enum', + 'label' => 'Repeat End Options', + 'values' => [ + '1' => 'Ends on a date', + '2' => 'Ends after a specific number of events', + ], + 'onchange' => "return AspenDiscovery.Events.toggleEndOptions(this.value);" + ], + 'recurrenceEnd' => [ + 'property' => 'recurrenceEnd', + 'type' => 'date', + 'label' => 'Ends After', + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();" + ], + 'recurrenceCount' => [ + 'property' => 'recurrenceCount', + 'type' => 'integer', + 'label' => 'Times to repeat', + 'hiddenByDefault' => true, + 'onchange' => "return AspenDiscovery.Events.calculateRecurrenceDates();" + ], + ], + ], + 'datesPreview' => [ + 'property' => 'datesPreview', + 'type' => 'label', + 'label' => 'Date Preview', + 'note' => 'To update, change the scheduling options above', + 'readOnly' => true, + 'hiddenByDefault' => true, + ], + ]; + $structure['infoSection']['expandByDefault'] = false; + $structure['scheduleSection']['hiddenByDefault'] = false; + } + return $structure; + } + + public function update($context = '') { + $ret = parent::update(); + if ($ret !== FALSE) { + $this->saveLibraries(); + $this->saveLocations(); + $this->saveFields(); + $this->generateInstances(); + } + return $ret; + } + + public function insert($context = '') { + $ret = parent::insert(); + if ($ret !== FALSE) { + $this->saveLibraries(); + $this->saveLocations(); + $this->saveFields(); + $this->generateInstances(); + } + return $ret; + } + + public function __set($name, $value) { + if ($name == 'libraries') { + $this->setLibraries($value); + } else if ($name == 'locations'){ + $this->setLocations($value); + } else if (is_numeric($name)) { + $this->setTypeField($name, $value); + } else if ($name == 'startDateForList') { + $this->_startDateForList = $this->startDate; + } else if ($name == 'dates') { + $this->setDates($value); + } else if ($name == 'datesPreview') { + $this->setDatesPreview($value); + } else { + parent::__set($name, $value); + } + } + + public function __get($name) { + if ($name == 'libraries') { + return $this->getLibraries(); + } else if ($name == 'locations') { + return $this->getLocations(); + } else if (is_numeric($name)){ + return $this->getTypeField($name); + } else if ($name == 'startDateForList') { + return parent::__get('startDate'); + } else if ($name == 'dates') { + return $this->getExistingDates(); + } else if ($name == 'instanceCount') { + return $this->getInstanceCount(); + } else if ($name == 'datesPreview') { + return $this->getDatesPreview(); + } else if ($name == 'endDate') { + return $this->calculateEnd($name); + } else if ($name == 'endTime') { + return $this->calculateEnd($name); + } else { + return parent::__get($name); + } + } + + public function getNumericColumnNames(): array { + return [ + 'private', + 'eventLength', + 'recurrenceOption', + 'recurrenceCount', + 'recurrenceInterval', + 'recurrenceFrequency', + 'monthlyOptions', + 'monthDay', + 'monthDate', + 'monthOffset', + 'endOption', + ]; + } + + public function getAdditionalListActions(): array { + $objectActions[] = [ + 'text' => 'Edit Specific Dates', + 'url' => '/Events/EventInstances?objectAction=edit&id=' . $this->id, + ]; + + return $objectActions; + } + + public function setLibraries($value) { + $this->_libraries = $value; + } + + public function setLocations($value) { + $this->_locations = $value; + } + + public function getLibraries() { + if (!isset($this->_libraries) && $this->id) { + $this->_libraries = []; + $library = new EventTypeLibrary(); + $library->eventTypeId = $this->id; + $library->find(); + while ($library->fetch()) { + $this->_libraries[$library->libraryId] = clone($library); + } + } + return $this->_libraries; + } + + public function getLocations() { + if (!isset($this->_locations) && $this->id) { + $this->_locations = []; + $location = new EventTypeLocation(); + $location->eventTypeId = $this->id; + $location->find(); + while ($location->fetch()) { + $this->_locations[$location->locationId] = clone($location); + } + } + return $this->_locations; + } + + public function saveLibraries() { + if (isset($this->_libraries) && is_array($this->_libraries)) { + $this->clearLibraries(); + + foreach ($this->_libraries as $library) { + $eventTypeLibrary = new EventTypeLibrary(); + $eventTypeLibrary->libraryId = $library; + $eventTypeLibrary->eventTypeId = $this->id; + $eventTypeLibrary->update(); + } + unset($this->_libraries); + } + } + + public function saveLocations() { + if (isset($this->_locations) && is_array($this->_locations)) { + $this->clearLocations(); + + foreach ($this->_locations as $location) { + $eventTypeLocation = new EventTypeLocation(); + $eventTypeLocation->locationId = $location; + $eventTypeLocation->eventTypeId = $this->id; + $eventTypeLocation->update(); + } + unset($this->_locations); + } + } + + public function generateInstances() { + // If event doesn't repeat and there is a start date, time and event length + if (isset($this->startDate) && isset($this->startTime) && isset($this->eventLength)) { + $todayDate = date('Y-m-d'); + $todayTime = date('H:i:s'); + if ($this->recurrenceOption == '1') { + // Don't generate dates in the past + if ($this->startDate > $todayDate || ($this->startDate == $todayDate && $this->startTime > $todayTime)) { + $instance = new EventInstance(); + $instance->eventId = $this->id; + $instance->find(true); // Update event if it already exists + $instance->date = $this->startDate; + $instance->time = $this->startTime; + $instance->length = $this->eventLength; + $instance->update(); + } + } else { // If event does repeat and there are preview dates + if ($this->_dates && is_array($this->_dates)) { + if (in_array('dates', $this->_changedFields)) { + // Should add more user options about which events to change + $this->clearFutureInstances(); + foreach ($this->_dates as $date) { + // Don't create instances in the past + if ($date > $todayDate || ($date == $todayDate && $this->startTime > $todayTime)) { + $instance = new EventInstance(); + $instance->eventId = $this->id; + $instance->date = $date; + $instance->time = $this->startTime; + $instance->length = $this->eventLength; + $instance->update(); + } + } + } + } + } + } + } + + private function clearFutureInstances() { + $instance = new EventInstance(); + $instance->eventId = $this->id; + $instance->find(); + $todayDate = date('Y-m-d'); + $todayTime = date('H:i:s'); + $instance->whereAdd("date > '$todayDate' OR (date = '$todayDate' and time > '$todayTime')"); + while ($instance->fetch()) { + $instance->delete(true); + } + } + + + private function clearLibraries() { + //Unset existing library associations + $eventTypeLibrary = new EventTypeLibrary(); + $eventTypeLibrary->eventTypeId = $this->id; + $eventTypeLibrary->find(); + while ($eventTypeLibrary->fetch()){ + $eventTypeLibrary->delete(true);; + } + } + + private function clearLocations() { + //Unset existing library associations + $eventTypeLocation = new EventTypeLocation(); + $eventTypeLocation->eventTypeId = $this->id; + $eventTypeLocation->find(); + while ($eventTypeLocation->fetch()){ + $eventTypeLocation->delete(true); + } + } + + public function setTypeField($fieldId, $value) { + $this->_typeFields[$fieldId] = $value; + } + + public function setDates ($value) { + $value = explode(',', $value); + $this->_dates = $value; + } + + public function setDatesPreview($value) { + $this->_datesPreview = $value; + } + + public function getTypeField($fieldId) { + if (!isset($this->_typeFields[$fieldId]) && $this->id) { + $this->_typeFields[$fieldId] = ''; + $field = new EventEventField(); + $field->eventId = $this->id; + $field->eventFieldId = $fieldId; + if ($field->find(true)) { + $this->_typeFields[$fieldId] = $field->value; + } + } + return $this->_typeFields[$fieldId] ?? ''; + } + + public function getDatesPreview() { + if (!isset($this->_dates) && $this->id) { + $this->_datesPreview = ''; + $instance = new EventInstance(); + $instance->eventId = $this->id; + $instance->find(); + while ($instance->fetch()) { + $date = strtotime($instance->date); + if ($date) { + $this->_datesPreview .= date("l, F jS, Y", $date) . "; "; + } + } + } + return $this->_datesPreview ?? ''; + } + + public function getExistingDates() { + if (!isset($this->_dates) && $this->id) { + $this->_dates = []; + $instance = new EventInstance(); + $instance->eventId = $this->id; + $instance->find(); + while ($instance->fetch()) { + $this->_dates[] = $instance->date; + } + } + if (is_array($this->_dates)) { + return implode(",", $this->_dates); + } else { + return $this->_dates; + } + } + + public function getInstanceCount() { + if (!isset($this->_instanceCount) && $this->id) { + $this->_instanceCount = ''; + $instance = new EventInstance(); + $instance->eventId = $this->id; + $todayDate = date('Y-m-d'); + $todayTime = date('H:i:s'); + $instance->whereAdd("date > '$todayDate' OR (date = '$todayDate' and time > '$todayTime')"); + $this->_instanceCount = $instance->count(); + } + return $this->_instanceCount; + } + + public function saveFields() { + if (isset($this->_typeFields) && is_array($this->_typeFields)) { + $this->clearFields(); + + foreach ($this->_typeFields as $fieldId => $field) { + $eventField = new EventEventField(); + $eventField->eventFieldId = $fieldId; + $eventField->eventId = $this->id; + if ($field == "on") { // Handle checkboxes + $eventField->value = 1; + } else { + $eventField->value = $field; + } + $eventField->update(); + } + unset($this->_typeFields); + } + } + + private function clearFields() { + //Delete existing field associations + $eventField = new EventEventField(); + $eventField->eventId = $this->id; + $eventField->find(); + while ($eventField->fetch()){ + $eventField->delete(true); + } + } + + public function getEventType() { + if (isset($this->eventTypeId)) { + $eventType = new EventType(); + $eventType->id = $this->eventTypeId; + if ($eventType->find(true)) { + return $eventType; + } + } + } + + public function calculateEnd($fieldName) { + if (isset($this->startDate) && isset($this->startTime) && isset($this->eventLength)) { + $dateTime = new \DateTime($this->startDate . ' ' . $this->startTime); + $dateTime->modify('+' . $this->eventLength . ' hours'); + $endDate = $dateTime->format('Y-m-d'); + $endTime = $dateTime->format('H:i:s'); + } + if ($fieldName == "endTime") { + return $endTime; + } else if ($fieldName == "endDate") { + return $endDate; + } + return NULL; + } + + public function updateStructureForEditingObject($structure) : array { + if ($eventType = $this->getEventType()) { + if (!empty($this->eventTypeId)) { + if (empty($this->title)) { + $this->title = $eventType->title; + } + if (!$eventType->titleCustomizable) { + $this->title = $eventType->title; + $structure['title']['readOnly'] = true; + } + if (empty($this->description)) { + $this->description = $eventType->description; + } + if (!$eventType->descriptionCustomizable) { + $this->description = $eventType->description; + $structure['infoSection']['properties']['description']['readOnly'] = true; + } + if (empty($this->cover)) { + $this->cover = $eventType->cover; + } + if (!$eventType->coverCustomizable) { + $structure['infoSection']['properties']['cover']['readOnly'] = true; + $this->cover = $eventType->cover; + } + if (empty($this->eventLength)) { + $this->eventLength = $eventType->eventLength; + } + if (!$eventType->lengthCustomizable) { + $structure['scheduleSection']['properties']['eventLength']['readOnly'] = true; + $this->eventLength = $eventType->eventLength; + } + $structure['infoSection']['properties']['fieldSetFieldSection']['properties'] = $eventType->getFieldSetFields(); + // Update scheduling sections + switch ($this->recurrenceOption) { + case '2': + // daily repeat + $structure['scheduleSection']['properties']['frequencySection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['repeatEndsSection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['datesPreview']['hiddenByDefault'] = false; + break; + case '6': // every weekday - same as weekly + case '3': + // weekly repeats + $structure['scheduleSection']['properties']['frequencySection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['weeklySection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['repeatEndsSection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['datesPreview']['hiddenByDefault'] = false; + break; + case '4': + // monthly repeats + $structure['scheduleSection']['properties']['frequencySection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['monthlySection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['repeatEndsSection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['datesPreview']['hiddenByDefault'] = false; + break; + case '5': + // annual repeats + $structure['scheduleSection']['properties']['frequencySection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['repeatEndsSection']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['datesPreview']['hiddenByDefault'] = false; + break; + case '7': + // custom + $structure['scheduleSection']['properties']['frequencySection']['hiddenByDefault'] = false; + if ($this->recurrenceFrequency == '2') { + $structure['scheduleSection']['properties']['weeklySection']['hiddenByDefault'] = false; + } else if ($this->recurrenceFrequency == '3') { + $structure['scheduleSection']['properties']['monthlySection']['hiddenByDefault'] = false; + } + break; + } + switch ($this->monthlyOption) { + case '1': + $structure['scheduleSection']['properties']['monthlySection']['properties']['weekNumber']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['monthlySection']['properties']['monthDay']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['monthlySection']['properties']['monthDate']['hiddenByDefault'] = true; + $structure['scheduleSection']['properties']['monthlySection']['properties']['monthOffset']['hiddenByDefault'] = false; + break; + case '2': + $structure['scheduleSection']['properties']['monthlySection']['properties']['weekNumber']['hiddenByDefault'] = true; + $structure['scheduleSection']['properties']['monthlySection']['properties']['monthDay']['hiddenByDefault'] = true; + $structure['scheduleSection']['properties']['monthlySection']['properties']['monthDate']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['monthlySection']['properties']['monthOffset']['hiddenByDefault'] = true; + } + switch ($this->endOption) { + case '1': + $structure['scheduleSection']['properties']['repeatEndsSection']['properties']['recurrenceEnd']['hiddenByDefault'] = false; + $structure['scheduleSection']['properties']['repeatEndsSection']['properties']['recurrenceCount']['hiddenByDefault'] = true; + break; + case '2': + $structure['scheduleSection']['properties']['repeatEndsSection']['properties']['recurrenceEnd']['hiddenByDefault'] = true; + $structure['scheduleSection']['properties']['repeatEndsSection']['properties']['recurrenceCount']['hiddenByDefault'] = false; + } + } + } + return $structure; + } +} diff --git a/code/web/sys/Events/EventEventField.php b/code/web/sys/Events/EventEventField.php new file mode 100644 index 0000000000..7b0fa625a4 --- /dev/null +++ b/code/web/sys/Events/EventEventField.php @@ -0,0 +1,9 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id', + ], + 'name' => [ + 'property' => 'name', + 'type' => 'text', + 'label' => 'Name', + 'description' => 'A name for the field', + ], + 'description' => [ + 'property' => 'description', + 'type' => 'text', + 'label' => 'Description/Instructions for usage', + 'description' => 'A description or instructions for the field', + ], + 'type' => [ + 'property' => 'type', + 'type' => 'enum', + 'label' => 'Field Type', + 'description' => 'The type of field', + 'values' => [ + '0' => 'Text Field', + '1' => 'Text Area', + '2' => 'Checkbox', + '3' => 'Select List', + '4' => 'Email Address', + '5' => 'URL', + ], + 'default' => '0', + ], + 'allowableValues' => [ + 'property' => 'allowableValues', + 'type' => 'text', + 'label' => 'Allowable Values for Select Lists', + 'description' => 'A comma-separated list of allowable values (only for select lists)', + ], + 'defaultValue' => [ + 'property' => 'defaultValue', + 'type' => 'text', + 'label' => 'Default Value', + 'description' => 'The default value for the field', + ], + 'facetName' => [ + 'property' => 'facetName', + 'type' => 'enum', + 'label' => 'Facet Name', + 'values' => [ + '0' => 'None', + '1' => 'Age Group', + '2' => 'Program Type', + '3' => 'Category', + '4' => 'Custom Facet 1', + '5' => 'Custom Facet 2', + '6' => 'Custom Facet 3', + ], + 'default' => '0', + ], + ]; + return $structure; + } + + public static function getEventFieldList(): array { + $fieldList = []; + $object = new EventField(); + $object->orderBy('name'); + $object->find(); + while ($object->fetch()) { + $label = $object->name . " - " . $object->description; + $fieldList[$object->id] = $label; + } + return $fieldList; + } +} + diff --git a/code/web/sys/Events/EventFieldSet.php b/code/web/sys/Events/EventFieldSet.php new file mode 100644 index 0000000000..d78267c1c1 --- /dev/null +++ b/code/web/sys/Events/EventFieldSet.php @@ -0,0 +1,169 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id', + ], + 'name' => [ + 'property' => 'name', + 'type' => 'text', + 'label' => 'Name', + 'description' => 'A name for the field', + ], + 'eventFields' => [ + 'property' => 'eventFields', + 'type' => 'multiSelect', + 'listStyle' => 'checkboxSimple', + 'label' => 'Event Fields', + 'description' => 'The event fields that make up the set', + 'values' => $eventFields, + ] + ]; + return $structure; + } + + public function update($context = '') { + $ret = parent::update(); + if ($ret !== FALSE) { + $this->saveFields(); + } + return $ret; + } + + public function insert($context = '') { + $ret = parent::insert(); + if ($ret !== FALSE) { + $this->saveFields(); + } + return $ret; + } + + public function __set($name, $value) { + if ($name == 'eventFields') { + $this->setEventFields($value); + } else { + parent::__set($name, $value); + } + } + + public function __get($name) { + if ($name == 'eventFields') { + return $this->getEventFields(); + } else { + return parent::__get($name); + } + } + + public function setEventFields($value) { + $this->_eventFields = $value; + } + + public function getEventFields() { + if (!isset($this->_eventFields) && $this->id) { + $this->_eventFields = []; + $field = new EventFieldSetField(); + $field->eventFieldSetId = $this->id; + $field->find(); + while ($field->fetch()) { + $this->_eventFields[$field->eventFieldId] = clone($field); + } + } + return $this->_eventFields; + } + + public function saveFields() { + if (isset($this->_eventFields) && is_array($this->_eventFields)) { + $this->clearFields(); + + foreach ($this->_eventFields as $eventField) { + $fieldSetFields = new EventFieldSetField(); + $fieldSetFields->eventFieldId = $eventField; + $fieldSetFields->eventFieldSetId = $this->id; + $fieldSetFields->update(); + } + unset($this->_eventFields); + } + } + + private function clearFields() { + //Delete existing field/field set associations + $fieldSetFields = new EventFieldSetField(); + $fieldSetFields->eventFieldSetId = $this->id; + $fieldSetFields->find(); + while ($fieldSetFields->fetch()){ + $fieldSetFields->delete(true);; + } + } + + public static function getEventFieldSetList(): array { + $setList = []; + $object = new EventFieldSet(); + $object->orderBy('name'); + $object->find(); + while ($object->fetch()) { + $label = $object->name; + $setList[$object->id] = $label; + } + return $setList; + } + + public function getFieldObjectStructure() { + $structure = []; + foreach ($this->getEventFields() as $fieldId) { + $field = new EventField(); + $field->id = $fieldId->eventFieldId; + if ($field->find(true)) { + switch($field->type) { + case 0: + $type = 'text'; + break; + case 1: + $type = 'textarea'; + break; + case 2: + $type = 'checkbox'; + break; + case 3: + $type = 'enum'; + break; + case 4: + $type = 'email'; + break; + case 5: + $type = 'url'; + break; + default: + $type = ''; + break; + } + $structure[$field->id] = [ + 'fieldId' => $field->id, + 'property' => $field->id, + 'type' => $type, + 'label' => $field->name, + 'description' => $field->description, + 'default' => $field->defaultValue, + 'facetName' => $field->facetName, + ]; + if ($type == 'enum') { + $structure[$field->id]['values'] = explode(",", $field->allowableValues); + } + } + } + return $structure; + } +} diff --git a/code/web/sys/Events/EventFieldSetField.php b/code/web/sys/Events/EventFieldSetField.php new file mode 100644 index 0000000000..77d9f43686 --- /dev/null +++ b/code/web/sys/Events/EventFieldSetField.php @@ -0,0 +1,8 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id', + ], + 'eventId' => [ + 'property' => 'eventId', + 'type' => 'text', + 'label' => 'Event Name', + 'description' => 'A name for the field', + 'hiddenByDefault' => true, + 'hideInLists' => true, + ], + 'date' => [ + 'property' => 'date', + 'type' => 'date', + 'label' => 'Event Date', + 'description' => 'The event date', + ], + 'time' => [ + 'property' => 'time', + 'type' => 'time', + 'label' => 'Event Time', + 'description' => 'The event Time', + ], + 'length' => [ + 'property' => 'length', + 'type' => 'integer', + 'label' => 'Length (Hours)', + 'description' => 'The event length in hours', + ], + 'note' => [ + 'property' => 'note', + 'type' => 'text', + 'label' => 'Note', + 'description' => 'A note for this specific instance', + ], + 'status' => [ + 'property' => 'status', + 'type' => 'checkbox', + 'label' => 'Active', + 'default' => 1, + 'description' => 'Whether the event is active or cancelled', + ] + ]; + return $structure; + } + +// public function update($context = '') { +// $ret = parent::update(); +// if ($ret !== FALSE) { +// $this->saveFields(); +// } +// return $ret; +// } +// +// public function insert($context = '') { +// $ret = parent::insert(); +// if ($ret !== FALSE) { +// $this->saveFields(); +// } +// return $ret; +// } +// +// public function __set($name, $value) { +// if ($name == 'eventFields') { +// $this->setEventFields($value); +// } else { +// parent::__set($name, $value); +// } +// } +// +// public function __get($name) { +// if ($name == 'eventFields') { +// return $this->getEventFields(); +// } else { +// return parent::__get($name); +// } +// } +// +// public function setEventFields($value) { +// $this->_eventFields = $value; +// } +// +// public function getEventFields() { +// if (!isset($this->_eventFields) && $this->id) { +// $this->_eventFields = []; +// $field = new EventFieldSetField(); +// $field->eventFieldSetId = $this->id; +// $field->find(); +// while ($field->fetch()) { +// $this->_eventFields[$field->eventFieldId] = clone($field); +// } +// } +// return $this->_eventFields; +// } +// +// public function saveFields() { +// if (isset($this->_eventFields) && is_array($this->_eventFields)) { +// $this->clearFields(); +// +// foreach ($this->_eventFields as $eventField) { +// $fieldSetFields = new EventFieldSetField(); +// $fieldSetFields->eventFieldId = $eventField; +// $fieldSetFields->eventFieldSetId = $this->id; +// $fieldSetFields->update(); +// } +// unset($this->_eventFields); +// } +// } +// +// private function clearFields() { +// //Delete existing field/field set associations +// $fieldSetFields = new EventFieldSetField(); +// $fieldSetFields->eventFieldSetId = $this->id; +// $fieldSetFields->find(); +// while ($fieldSetFields->fetch()){ +// $fieldSetFields->delete(true);; +// } +// } +// +// public static function getEventFieldSetList(): array { +// $setList = []; +// $object = new EventFieldSet(); +// $object->orderBy('name'); +// $object->find(); +// while ($object->fetch()) { +// $label = $object->name; +// $setList[$object->id] = $label; +// } +// return $setList; +// } +} \ No newline at end of file diff --git a/code/web/sys/Events/EventInstanceGroup.php b/code/web/sys/Events/EventInstanceGroup.php new file mode 100644 index 0000000000..068efc0120 --- /dev/null +++ b/code/web/sys/Events/EventInstanceGroup.php @@ -0,0 +1,183 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id within the database', + ], + 'title' => [ + 'property' => 'title', + 'type' => 'text', + 'label' => 'Display Name', + 'description' => 'The name of the event', + 'readOnly' => true, + ], + 'startDate' => [ + 'property' => 'startDate', + 'type' => 'date', + 'label' => 'Date', + 'description' => 'Event start date', + 'readOnly' => true, + ], + 'instances' => [ + 'property' => 'instances', + 'type' => 'oneToMany', + 'label' => 'Instances', + 'description' => 'A list of instances for this event', + 'keyThis' => 'id', + 'keyOther' => 'id', + 'subObjectType' => 'EventInstance', + 'structure' => $instanceStructure, + 'additionalOneToManyActions' => [ + 'showPastEvents' => [ + 'text' => 'Show Past Events', + 'url' => '/Events/EventInstances?id=$id&objectAction=edit&pastEvents=true', + ], + ], + 'storeDb' => true, + 'allowEdit' => true, + 'canDelete' => true, + ], + ]; + } + public function update($context = '') { + $ret = parent::update(); + if ($ret !== FALSE) { + $this->saveInstances(); + } + return $ret; + } + + public function insert($context = '') { + $ret = parent::insert(); + if ($ret !== FALSE) { + $this->saveInstances(); + } + return $ret; + } + + public function saveInstances() { + if (isset ($this->_instances) && is_array($this->_instances)) { + $this->saveOneToManyOptions($this->_instances, 'eventId'); + unset($this->_instances); + } + } + + public function __get($name) { + if ($name == 'instances') { + if (isset($_GET['pastEvents']) && $_GET['pastEvents'] == 'true') { + return $this->getAllInstances(); + } + return $this->getFutureInstances(); +// } if ($name == "libraries") { +// return $this->getLibraries(); + } else { + return parent::__get($name); + } + } + + public function __set($name, $value) { + if ($name == 'instances') { + $this->setInstances($value); +// }if ($name == "libraries") { +// $this->_libraries = $value; + } else { + parent::__set($name, $value); + } + } + + /** @return EventInstance[] */ + public function getFutureInstances(): ?array { + if (!isset($this->_instances) && $this->id) { + $this->_instances = []; + $instance = new EventInstance(); + $instance->eventId = $this->id; + $todayDate = date('Y-m-d'); + $todayTime = date('H:i:s'); + $instance->whereAdd("date > '$todayDate' OR (date = '$todayDate' and time > '$todayTime')"); + $instance->find(); + while ($instance->fetch()) { + $this->_instances[$instance->id] = clone($instance); + } + } + return $this->_instances; + } + + /** @return EventInstance[] */ + public function getAllInstances(): ?array { + if (!isset($this->_instances) && $this->id) { + $this->_instances = []; + $instance = new EventInstance(); + $instance->eventId = $this->id; + $instance->find(); + while ($instance->fetch()) { + $this->_instances[$instance->id] = clone($instance); + } + } + return $this->_instances; + } + + public function setInstances($value) { + $this->_instances = $value; + } + + public function clearInstances() { + $this->clearOneToManyOptions('EventInstance', 'eventId'); + /** @noinspection PhpUndefinedFieldInspection */ + $this->_instances = []; + } + +// public function getLibraries() { +// if (!isset($this->_libraries) && $this->id) { +// $this->_libraries = []; +// $library = new LibraryEventsSetting(); +// $library->eventsFacetSettingsId = $this->id; +// $library->find(); +// while ($library->fetch()) { +// $this->_libraries[$library->libraryId] = $library->libraryId; +// } +// } +// return $this->_libraries; +// } +// private function clearLibraries() { +// //Delete links to the libraries +// $libraryEventSetting = new LibraryEventsSetting(); +// $libraryEventSetting->eventsFacetSettingsId = $this->id; +// while ($libraryEventSetting->fetch()){ +// $libraryEventSetting->eventsFacetSettingsId = "0"; +// $libraryEventSetting->update(); +// } +// } +// public function saveLibraries() { +// if (isset($this->_libraries) && is_array($this->_libraries)) { +// $this->clearLibraries(); +// +// foreach ($this->_libraries as $libraryId) { +// $libraryEventSetting = new LibraryEventsSetting(); +// $libraryEventSetting->libraryId = $libraryId; +// +// while ($libraryEventSetting->fetch()){ //if there is no event setting for a library, that library won't save because there's nothing to update +// $libraryEventSetting->eventsFacetSettingsId = $this->id; +// $libraryEventSetting->update(); +// } +// } +// unset($this->_libraries); +// } +// } +// +} \ No newline at end of file diff --git a/code/web/sys/Events/EventType.php b/code/web/sys/Events/EventType.php new file mode 100644 index 0000000000..4782c6c3f9 --- /dev/null +++ b/code/web/sys/Events/EventType.php @@ -0,0 +1,281 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id', + ], + 'title' => [ + 'property' => 'title', + 'type' => 'text', + 'label' => 'Title', + 'description' => 'The default title for this type of event', + ], + 'titleCustomizable' => [ + 'property' => 'titleCustomizable', + 'type' => 'checkbox', + 'label' => 'Title Customizable?', + 'default' => true, + 'description' => 'Can users change the title for individual events of this type?', + ], + 'description' => [ + 'property' => 'description', + 'type' => 'text', + 'label' => 'Description', + 'description' => 'The default description for this type of event', + ], + 'descriptionCustomizable' => [ + 'property' => 'descriptionCustomizable', + 'type' => 'checkbox', + 'label' => 'Description Customizable?', + 'default' => true, + 'description' => 'Can users change the description for individual events of this type?', + ], + 'cover' => [ + 'property' => 'cover', + 'type' => 'image', + 'label' => 'Cover', + 'maxWidth' => 280, + 'maxHeight' => 280, + 'description' => 'The default cover image for this type of event', + 'hideInLists' => true, + ], + 'coverCustomizable' => [ + 'property' => 'coverCustomizable', + 'type' => 'checkbox', + 'label' => 'Cover Customizable?', + 'default' => true, + 'description' => 'Can users change the cover for individual events of this type?', + ], + 'eventLength' => [ + 'property' => 'eventLength', + 'type' => 'integer', + 'label' => 'Event Length (Hours)', + 'description' => 'The default event length (in hours) for this type of event', + ], + 'lengthCustomizable' => [ + 'property' => 'lengthCustomizable', + 'type' => 'checkbox', + 'label' => 'Length Customizable?', + 'default' => true, + 'description' => 'Can users change the event length for individual events of this type?', + ], + 'libraries' => [ + 'property' => 'libraries', + 'type' => 'multiSelect', + 'listStyle' => 'checkboxSimple', + 'label' => 'Libraries', + 'description' => 'Define libraries that use this type', + 'values' => $libraryList, + ], + 'locations' => [ + 'property' => 'locations', + 'type' => 'multiSelect', + 'listStyle' => 'checkboxSimple', + 'label' => 'Locations', + 'description' => 'Define locations that use this type', + 'values' => $locationList, + ], + 'eventFieldSetId' => [ + 'property' => 'eventFieldSetId', + 'type' => 'enum', + 'label' => 'Event Field Set', + 'description' => 'The event field set that contains the right fields to use with this event type', + 'values' => $eventSets, + 'required' => true, + ], + 'archived' => [ + 'property' => 'archived', + 'type' => 'checkbox', + 'label' => 'Archive?', + 'default' => false, + 'description' => 'An archived event type will no longer show up as an option for events but can be restored later', + ] + ]; + return $structure; + } + + public function update($context = '') { + $ret = parent::update(); + if ($ret !== FALSE) { + $this->saveLibraries(); + $this->saveLocations(); + } + return $ret; + } + + public function insert($context = '') { + $ret = parent::insert(); + if ($ret !== FALSE) { + $this->saveLibraries(); + $this->saveLocations(); + } + return $ret; + } + + public function __set($name, $value) { + if ($name == 'libraries') { + $this->setLibraries($value); + } else if ($name == 'locations'){ + $this->setLocations($value); + } else + { + parent::__set($name, $value); + } + } + + public function __get($name) { + if ($name == 'libraries') { + return $this->getLibraries(); + } else if ($name == 'locations') { + return $this->getLocations(); + } else { + return parent::__get($name); + } + } + + public function setLibraries($value) { + $this->_libraries = $value; + } + + public function setLocations($value) { + $this->_locations = $value; + } + + public function getLibraries() { + if (!isset($this->_libraries) && $this->id) { + $this->_libraries = []; + $library = new EventTypeLibrary(); + $library->eventTypeId = $this->id; + $library->find(); + while ($library->fetch()) { + $this->_libraries[$library->libraryId] = clone($library); + } + } + return $this->_libraries; + } + + public function getLocations() { + if (!isset($this->_locations) && $this->id) { + $this->_locations = []; + $location = new EventTypeLocation(); + $location->eventTypeId = $this->id; + $location->find(); + while ($location->fetch()) { + $this->_locations[$location->locationId] = clone($location); + } + } + return $this->_locations; + } + + public function saveLibraries() { + if (isset($this->_libraries) && is_array($this->_libraries)) { + $this->clearLibraries(); + + foreach ($this->_libraries as $library) { + $eventTypeLibrary = new EventTypeLibrary(); + $eventTypeLibrary->libraryId = $library; + $eventTypeLibrary->eventTypeId = $this->id; + $eventTypeLibrary->update(); + } + unset($this->_libraries); + } + } + + public function saveLocations() { + if (isset($this->_locations) && is_array($this->_locations)) { + $this->clearLocations(); + + foreach ($this->_locations as $location) { + $eventTypeLocation = new EventTypeLocation(); + $eventTypeLocation->locationId = $location; + $eventTypeLocation->eventTypeId = $this->id; + $eventTypeLocation->update(); + } + unset($this->_locations); + } + } + + + private function clearLibraries() { + //Unset existing library associations + $eventTypeLibrary = new EventTypeLibrary(); + $eventTypeLibrary->eventTypeId= $this->id; + $eventTypeLibrary->find(); + while ($eventTypeLibrary->fetch()){ + $eventTypeLibrary->delete(true);; + } + } + + private function clearLocations() { + //Unset existing library associations + $eventTypeLocation = new EventTypeLocation(); + $eventTypeLocation->eventTypeId= $this->id; + $eventTypeLocation->find(); + while ($eventTypeLocation->fetch()) { + $eventTypeLocation->delete(true); + } + } + + public static function getEventTypeList(): array { + $typeList = []; + $object = new EventType(); + $object->orderBy('title'); + $object->find(); + while ($object->fetch()) { + $label = $object->title; + $typeList[$object->id] = $label; + } + return $typeList; + } + + public static function getEventTypeIdsForLocation(string $locationId): array { + $typeIds = []; + $typeLocation = new EventTypeLocation(); + $typeLocation->locationId = $locationId; + $typeLocation->find(); + while ($typeLocation->fetch()) { + $typeIds[] = $typeLocation->eventTypeId; + } + return $typeIds; + } + + public function getFieldSetFields() { + $fieldSet = new EventFieldSet(); + if ($this->eventFieldSetId) { + $fieldSet->id = $this->eventFieldSetId; + if ($fieldSet->find(true)) { + return $fieldSet->getFieldObjectStructure(); + } + } + return []; + } + +} diff --git a/code/web/sys/Events/EventTypeLibrary.php b/code/web/sys/Events/EventTypeLibrary.php new file mode 100644 index 0000000000..b4ea15013b --- /dev/null +++ b/code/web/sys/Events/EventTypeLibrary.php @@ -0,0 +1,8 @@ + [ + 'property' => 'id', + 'type' => 'label', + 'label' => 'Id', + 'description' => 'The unique id', + ], + 'runFullUpdate' => [ + 'property' => 'runFullUpdate', + 'type' => 'checkbox', + 'label' => 'Run Full Update', + 'description' => 'Whether or not a full update of all records should be done on the next pass of indexing', + 'default' => 0, + ], + 'numberOfDaysToIndex' => [ + 'property' => 'numberOfDaysToIndex', + 'type' => 'integer', + 'label' => 'Number of Days to Index', + 'description' => 'How many days in the future to index events', + 'default' => 365, + ], + 'lastUpdateOfAllEvents' => [ + 'property' => 'lastUpdateOfAllEvents', + 'type' => 'timestamp', + 'label' => 'Last Update Of All Events', + 'readOnly' => 1, + ], + 'lastUpdateOfChangedEvents' => [ + 'property' => 'lastUpdateOfChangedEvents', + 'type' => 'timestamp', + 'label' => 'Last Update Of Changed Events', + 'readOnly' => 1, + ], + ]; + } +} \ No newline at end of file diff --git a/code/web/sys/LibraryLocation/Sublocation.php b/code/web/sys/LibraryLocation/Sublocation.php index ccbc7c155c..e368edf990 100644 --- a/code/web/sys/LibraryLocation/Sublocation.php +++ b/code/web/sys/LibraryLocation/Sublocation.php @@ -11,6 +11,7 @@ class Sublocation extends DataObject { public $locationId; public $isValidHoldPickupAreaILS; public $isValidHoldPickupAreaAspen; + public $isValidEventLocation; private $_patronTypes; @@ -18,7 +19,8 @@ public function getNumericColumnNames(): array { return [ 'locationId', 'isValidHoldPickupAreaAspen', - 'isValidHoldPickupAreaILS' + 'isValidHoldPickupAreaILS', + 'isValidEventLocation', ]; } @@ -88,6 +90,12 @@ static function getObjectStructure($context = ''): array { 'description' => 'Whether or not this sublocation is a valid hold pickup area for Aspen', 'note' => 'Requires an ILS Id and Valid Hold Pickup Area (ILS) to be checked', ], + 'isValidEventLocation' => [ + 'property' => 'isValidEventLocation', + 'type' => 'checkbox', + 'label' => 'Valid Event Location', + 'description' => 'Whether or not this sublocation is valid for events', + ], 'patronTypes' => [ 'property' => 'patronTypes', 'type' => 'multiSelect', diff --git a/code/web/sys/SearchObject/EventsSearcher.php b/code/web/sys/SearchObject/EventsSearcher.php index 8a4b2e3afa..61465cc79f 100644 --- a/code/web/sys/SearchObject/EventsSearcher.php +++ b/code/web/sys/SearchObject/EventsSearcher.php @@ -376,6 +376,9 @@ public function getRecordDriverForResult($record) { } else if (substr($record['type'], 0, 13) == 'event_assabet') { require_once ROOT_DIR . '/RecordDrivers/AssabetEventRecordDriver.php'; return new AssabetEventRecordDriver($record); + } else if (substr($record['type'], 0, 17) == 'event_nativeEvent') { + require_once ROOT_DIR . '/RecordDrivers/NativeEventRecordDriver.php'; + return new NativeEventRecordDriver($record); } else { // TODO: rewrite Library Market Library Calendar type as event_lm or something similar. 2022 03 20 James. require_once ROOT_DIR . '/RecordDrivers/LibraryCalendarEventRecordDriver.php'; diff --git a/code/web/sys/SystemVariables.php b/code/web/sys/SystemVariables.php index 07e83751b1..efdb8efe19 100644 --- a/code/web/sys/SystemVariables.php +++ b/code/web/sys/SystemVariables.php @@ -32,6 +32,7 @@ class SystemVariables extends DataObject { public $offlineMessage; public $appScheme; public $enableBrandedApp; + public $enableAspenEvents; public $supportingCompany; public $googleBucket; public $trackIpAddresses; @@ -328,6 +329,13 @@ static function getObjectStructure($context = ''): array { 'description' => 'Whether or not the library can configure branded Aspen LiDA', 'default' => false, ], + 'enableAspenEvents' => [ + 'property' => 'enableAspenEvents', + 'type' => 'checkbox', + 'label' => 'Enable Aspen Events', + 'description' => 'Whether or not the library can configure Aspen Events', + 'default' => false, + ], 'supportingCompany' => [ 'property' => 'supportingCompany', 'type' => 'text', @@ -373,6 +381,7 @@ static function getObjectStructure($context = ''): array { $objectStructure['indexingSection']['properties']['indexVersion']['type'] = 'hidden'; $objectStructure['indexingSection']['properties']['searchVersion']['type'] = 'hidden'; $objectStructure['enableBrandedApp']['type'] = 'hidden'; + $objectStructure['enableAspenEvents']['type'] = 'hidden'; } return $objectStructure; diff --git a/data_dir_setup/solr7/events/conf/schema.xml b/data_dir_setup/solr7/events/conf/schema.xml index 3af98ebb58..d7c9c5194a 100644 --- a/data_dir_setup/solr7/events/conf/schema.xml +++ b/data_dir_setup/solr7/events/conf/schema.xml @@ -1,7 +1,7 @@ - + @@ -129,6 +129,11 @@ + + + + + @@ -137,7 +142,7 @@ - + identifier @@ -151,4 +156,4 @@ - \ No newline at end of file +