/home/edulekha/crm.edulekha.com/modules/appointly/appointly.php
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/*
Module Name: Appointly
Description: Perfex CRM Advanced CRM Appointments Module
Version: 1.3.5
Author: iDev
Author URI: https://idevalex.com
Requires at least: 3.0.0
*/
$CI = &get_instance();
define('APPOINTLY_MODULE_NAME', 'appointly');
define('APPOINTLY_SMS_APPOINTMENT_APPROVED_TO_CLIENT', 'appointly_appointment_approved_send_to_client');
define('APPOINTLY_SMS_APPOINTMENT_CANCELLED_TO_CLIENT', 'appointly_appointment_cancelled_to_client');
define('APPOINTLY_SMS_APPOINTMENT_APPOINTMENT_REMINDER_TO_CLIENT', 'appointly_appointment_reminder_to_client');
define('APPOINTLY_SMS_APPOINTMENT_UPDATED_TO_CLIENT', 'appointly_appointment_updated_to_client');
hooks()->add_action('admin_init', 'appointly_register_permissions');
hooks()->add_action('admin_init', 'appointly_register_menu_items');
hooks()->add_action('clients_init', 'appointly_clients_area_schedule_appointment');
hooks()->add_action('after_cron_run', 'appointly_send_email_templates');
hooks()->add_action('after_cron_run', 'appointly_recurring_events');
hooks()->add_action('app_admin_footer', 'appointly_add_filters_js');
hooks()->add_action('app_admin_footer', 'appointly_get_environment');
hooks()->add_action('after_email_templates', 'appointly_add_email_templates');
// Add appointments permission to client contact permissions
hooks()->add_filter('get_contact_permissions', 'appointly_add_contact_permission');
// Hooks moved from helper file
hooks()->add_action('app_admin_head', 'appointly_head_components');
hooks()->add_action('app_admin_footer', 'appointly_footer_components');
hooks()->add_filter('available_tracking_templates', 'add_appointment_approved_email_tracking');
register_merge_fields('appointly/merge_fields/appointly_merge_fields');
/**
* Functions moved from helper file for hooks
*/
/**
* Email tracking
*
* @param $slugs
*
* @return mixed
*/
if (! function_exists('add_appointment_approved_email_tracking')) {
function add_appointment_approved_email_tracking($slugs)
{
if (! in_array('appointment-approved-to-contact', $slugs)) {
$slugs[] = 'appointment-approved-to-contact';
}
return $slugs;
}
}
/**
* Injects theme CSS.
*/
if (! function_exists('appointly_head_components')) {
function appointly_head_components()
{
echo '<link href="' . module_dir_url(APPOINTLY_MODULE_NAME, 'assets/css/styles.css?v=' . time()) . '" rel="stylesheet" type="text/css">';
}
}
/**
* Injects theme JS for global modal.
*/
if (! function_exists('appointly_footer_components')) {
function appointly_footer_components()
{
echo '<script src="' . module_dir_url(APPOINTLY_MODULE_NAME, 'assets/js/global.js?v=' . time()) . '" type="text/javascript"></script>';
}
}
hooks()->add_filter('other_merge_fields_available_for', 'appointly_register_other_merge_fields');
hooks()->add_filter('available_merge_fields', 'appointly_allow_staff_merge_fields_for_appointment_templates');
hooks()->add_filter('get_dashboard_widgets', 'appointly_register_dashboard_widgets');
hooks()->add_filter('calendar_data', 'appointly_register_appointments_on_calendar', 10, 2);
/**
* Schedule appointment menu items in client area
*/
if (! function_exists('appointly_clients_area_schedule_appointment')) {
function appointly_clients_area_schedule_appointment()
{
if (get_option('appointly_show_clients_schedule_button') == 1 && ! is_client_logged_in()) {
add_theme_menu_item('schedule-appointment-id', [
'name' => _l('appointly_schedule_new_appointment'),
'href' => site_url('appointly/appointments_public/book?col=col-md-8+col-md-offset-2'),
'position' => 10,
]);
}
// Item is available for logged in clients if enabled in Setup->Settings->Appointment
if (is_client_logged_in()) {
if (get_option('appointly_tab_on_clients_page') == 1 && has_contact_permission('appointments')) {
// Add Appointments menu item instead of just "Schedule"
add_theme_menu_item('appointments', [
'name' => _l('appointment_appointments'),
'href' => site_url('appointly/appointment_clients/appointments'),
'position' => 5,
'icon' => 'fa-regular fa-calendar-check',
]);
}
}
}
}
/**
* Register appointments on staff and clients calendar.
*
* @param $data
* @param $config
*
* @return mixed
*/
function appointly_register_appointments_on_calendar($data, $config)
{
$CI = &get_instance();
$CI->load->model('appointly/appointly_model', 'apm');
// Get calendar data from the model
return $CI->apm->get_calendar_data($config['start'], $config['end'], $data);
}
hooks()->add_action('after_custom_fields_select_options', 'appointly_custom_fields');
/**
* Register new custom fields for
*
* @param $custom_field
*/
function appointly_custom_fields($custom_field)
{
$selected = (isset($custom_field) && $custom_field->fieldto == 'appointly') ? 'selected' : '';
echo '<option value="appointly" ' . ($selected) . '>' . _l('appointment_appointments') . '</option>';
}
/**
* Get today's appointments to render in dashboard widget.
*
* @param array $widgets
*
* @return array
*/
function appointly_register_dashboard_widgets($widgets)
{
// Add today's appointments widget if enabled
if (get_option('appointly_today_widget_enabled') == '1') {
$widgets[] = [
'container' => 'left-8',
'path' => 'appointly/widgets/today_appointments',
];
}
// Add upcoming appointments widget if enabled
if (get_option('appointly_upcoming_widget_enabled') == '1') {
$widgets[] = [
'container' => 'left-8',
'path' => 'appointly/widgets/upcoming_appointments',
];
}
return $widgets;
}
/**
* Get staff fields and insert into email templates for appointly.
*
* @param [array] $fields
*
* @return array
*/
function appointly_allow_staff_merge_fields_for_appointment_templates($fields)
{
$appointlyStaffFields = ['{staff_firstname}', '{staff_lastname}'];
foreach ($fields as $index => $group) {
foreach ($group as $key => $groupFields) {
if ($key == 'staff') {
foreach ($groupFields as $groupIndex => $groupField) {
if (in_array(
$groupField['key'],
$appointlyStaffFields,
true
)) {
$fields[$index][$key][$groupIndex]['available'] = array_merge(
$fields[$index][$key][$groupIndex]['available'],
['appointly']
);
}
}
break;
}
}
}
return $fields;
}
/**
* Register other merge fields for appointly.
*
* @param array $for
*
* @return array
*/
function appointly_register_other_merge_fields($for)
{
$for[] = 'appointly';
return $for;
}
/**
* Hook for assigning staff permissions for appointments module.
*/
function appointly_register_permissions()
{
$capabilities = [];
$capabilities['capabilities'] = [
'view' => _l('permission_view'),
'create' => _l('permission_create'),
'edit' => _l('permission_edit'),
'delete' => _l('permission_delete'),
'approve' => _l('permission_approve'),
'view_reports' => _l('permission_view_reports'),
];
register_staff_capabilities('appointments', $capabilities, _l('appointment_appointments'));
}
function appointly_get_environment()
{
echo '<script>document.addEventListener("DOMContentLoaded", function() { window.AppointlyEnv = "' . ENVIRONMENT . '"; });</script>';
}
/**
* Ensure menu item has all required keys to prevent position errors
*/
function appointly_ensure_menu_item_structure($item)
{
// Ensure all required keys exist with defaults
$defaults = [
'position' => 0,
'icon' => '',
'href' => '#',
];
return array_merge($defaults, $item);
}
/**
* Register new menu item in sidebar menu.
*/
function appointly_register_menu_items()
{
$CI = &get_instance();
if (staff_can('view', 'appointments')) {
$CI->app_menu->add_sidebar_menu_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'name' => _l('appointly_module_name'),
'href' => admin_url('appointly/appointments'),
'position' => 20,
'icon' => 'fa-solid fa-calendar-check',
]));
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-user-dashboard',
'name' => _l('appointment_appointments'),
'href' => admin_url('appointly/appointments'),
'position' => 1,
'icon' => 'fa-solid fa-table-list',
]));
}
if (staff_can('edit', 'appointments')) {
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-services',
'name' => _l('appointment_services_menu_label'),
'href' => admin_url('appointly/services'),
'position' => 2,
'icon' => 'fa-solid fa-briefcase',
]));
}
if (staff_can('edit', 'appointments')) {
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-company-schedule',
'name' => _l('appointly_company_schedule'),
'href' => admin_url('appointly/services/company_schedule'),
'position' => 3,
'icon' => 'fa-solid fa-business-time',
]));
}
if (staff_can('edit', 'appointments')) {
$position = is_admin() ? 4 : 3;
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-staff-working-hours',
'name' => _l('appointly_staff_working_hours'),
'href' => admin_url('appointly/services/staff_working_hours'),
'position' => $position,
'icon' => 'fa-solid fa-user-clock',
]));
}
$position = is_admin() ? 5 : 4;
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-user-history',
'name' => _l('appointment_history_label_menu_label'),
'href' => admin_url('appointly/appointments_history'),
'position' => $position,
'icon' => 'fa-solid fa-clock-rotate-left',
]));
if (staff_can('view_reports', 'appointments')) {
$position = is_admin() ? 6 : 5;
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-reports',
'name' => _l('appointment_analytics_and_reports_menu_label'),
'icon' => 'fa-solid fa-chart-line',
'href' => admin_url('appointly/reports'),
'position' => $position,
]));
}
$position = is_admin() ? 7 : 6;
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-link-menu-form',
'name' => _l('appointment_menu_form_link'),
'href' => site_url('appointly/appointments_public/book?col=col-md-8+col-md-offset-2'),
'href_attributes' => 'target="_blank" rel="noopener noreferrer"',
'position' => $position,
'icon' => 'fa-solid fa-link',
]));
if (staff_can('edit', 'appointments')) {
$position = is_admin() ? 8 : 7;
$CI->app_menu->add_sidebar_children_item(APPOINTLY_MODULE_NAME, appointly_ensure_menu_item_structure([
'slug' => 'appointly-settings',
'name' => _l('settings'),
'href' => admin_url('settings?group=appointly_settings'),
'position' => $position,
'icon' => 'fa-solid fa-sliders',
]));
}
}
/*
* Register activation hook
*/
register_activation_hook(APPOINTLY_MODULE_NAME, 'appointly_activation_hook');
/**
* The activation function.
*/
function appointly_activation_hook()
{
require __DIR__ . '/install.php';
// Automatically reset menu after installation to ensure Services menu appears
if (function_exists('update_option')) {
update_option('aside_menu_active', json_encode([]));
log_message('info', 'Appointly: Menu automatically reset after installation');
}
}
/*
* Register module language files
*/
register_language_files(APPOINTLY_MODULE_NAME, ['appointly']);
/*
* Loads the module function helper
*/
$CI->load->helper([APPOINTLY_MODULE_NAME . '/appointly', APPOINTLY_MODULE_NAME . '/appointly_google']);
/**
* Register cron email templates.
*/
function appointly_send_email_templates()
{
$CI = &get_instance();
$CI->load->model('appointly/appointly_attendees_model', 'atm');
// User events
$CI->db->where("(notification_date IS NULL AND reminder_before IS NOT NULL AND status = 'in-progress')");
$appointments = $CI->db->get(db_prefix() . 'appointly_appointments')->result_array();
$notified_users = [];
foreach ($appointments as $appointment) {
$date_compare = date('Y-m-d H:i', strtotime('+' . $appointment['reminder_before'] . ' ' . strtoupper($appointment['reminder_before_type'])));
if ($appointment['date'] . ' ' . $appointment['start_hour'] <= $date_compare) {
if (date('Y-m-d H:i', strtotime($appointment['date'] . ' ' . $appointment['start_hour'])) < date('Y-m-d H:i')) {
/*
* If appointment is missed then skip
*/
continue;
}
$attendees = $CI->atm->get($appointment['id']);
foreach ($attendees as $staff) {
add_notification([
'description' => 'appointment_you_have_new_appointment',
'touserid' => $staff['staffid'],
'fromcompany' => true,
'link' => 'appointly/appointments/view?appointment_id=' . $appointment['id'],
]);
$notified_users[] = $staff['staffid'];
send_mail_template('appointly_appointment_cron_reminder_to_staff', 'appointly', array_to_object($appointment), array_to_object($staff));
}
$template = mail_template('appointly_appointment_cron_reminder_to_contact', 'appointly', array_to_object($appointment));
$merge_fields = $template->get_merge_fields();
$template->send();
if ($appointment['by_sms'] == 1 && ! empty($appointment['phone'])) {
$CI->app_sms->trigger(APPOINTLY_SMS_APPOINTMENT_APPOINTMENT_REMINDER_TO_CLIENT, $appointment['phone'], $merge_fields);
}
$CI->db->where('id', $appointment['id']);
$CI->db->update('appointly_appointments', ['notification_date' => date('Y-m-d H:i:s')]);
}
}
pusher_trigger_notification(array_unique($notified_users));
}
function appointly_recurring_events()
{
$CI = &get_instance();
$tableAttendees = db_prefix() . 'appointly_attendees';
$table = db_prefix() . 'appointly_appointments';
// User events
$CI->db->where('recurring', 1);
$CI->db->where('(cycles != total_cycles OR cycles=0)');
$appointments = $CI->db->get(db_prefix() . 'appointly_appointments')->result_array();
foreach ($appointments as $appointment) {
$type = $appointment['recurring_type'];
$repeat_every = $appointment['repeat_every'];
$last_recurring_date = $appointment['last_recurring_date'];
$appointment_date = $appointment['date'];
// Current date Check if it is first recurring
if (! $last_recurring_date) {
$last_recurring_date = date('Y-m-d', strtotime($appointment_date));
} else {
$last_recurring_date = date('Y-m-d', strtotime($last_recurring_date));
}
$re_create_at = date(
'Y-m-d',
strtotime('+' . $repeat_every . ' ' . strtoupper($type), strtotime($last_recurring_date))
);
if (date('Y-m-d') >= $re_create_at) {
// Load model for availability checking
if (!isset($CI->appointly_model)) {
$CI->load->model('appointly/appointly_model');
}
// Check if the date is a company-wide blocked day
$blocked_days = get_appointly_blocked_days();
if (in_array($re_create_at, $blocked_days)) {
log_message('info', "Recurring appointment {$appointment['id']} skipped - date {$re_create_at} is blocked");
// Update last_recurring_date to skip this occurrence
$CI->db->where('id', $appointment['id']);
$CI->db->update($table, ['last_recurring_date' => $re_create_at]);
// Increment total_cycles to keep count accurate
$CI->db->where('id', $appointment['id']);
$CI->db->set('total_cycles', 'total_cycles+1', false);
$CI->db->update($table);
continue; // Skip this occurrence
}
// Check if provider is available on this date/time
if (!empty($appointment['provider_id']) && !empty($appointment['start_hour']) && !empty($appointment['duration'])) {
$available_slots = $CI->appointly_model->get_available_hours_by_date(
$appointment['provider_id'],
$re_create_at,
null, // exclude_appointment_id
$appointment['duration']
);
$start_time_check = date('H:i', strtotime($appointment['start_hour']));
$slot_available = false;
foreach ($available_slots as $slot) {
if ($slot['time'] === $start_time_check && $slot['available']) {
$slot_available = true;
break;
}
}
if (!$slot_available) {
log_message('warning', "Recurring appointment {$appointment['id']} skipped - time slot {$start_time_check} not available on {$re_create_at}");
// Update last_recurring_date to skip this occurrence
$CI->db->where('id', $appointment['id']);
$CI->db->update($table, ['last_recurring_date' => $re_create_at]);
// Increment total_cycles
$CI->db->where('id', $appointment['id']);
$CI->db->set('total_cycles', 'total_cycles+1', false);
$CI->db->update($table);
// Optionally notify admin about skipped recurring appointment
if (!empty($appointment['created_by'])) {
add_notification([
'description' => 'Recurring appointment skipped - time slot not available on ' . $re_create_at,
'touserid' => $appointment['created_by'],
'fromcompany' => true,
'link' => 'appointly/appointments/view?appointment_id=' . $appointment['id'],
]);
}
continue; // Skip this occurrence
}
}
// Ok, we can create the appointment - slot is available
$newAppointmentData = [];
$newAppointmentData['date'] = $re_create_at;
$newAppointmentData = array_merge($newAppointmentData, convertDateForDatabase($newAppointmentData['date']));
// Calendar integration fields (reset for new appointment)
$newAppointmentData['google_event_id'] = null;
$newAppointmentData['google_calendar_link'] = null;
$newAppointmentData['google_meet_link'] = null;
$newAppointmentData['google_added_by_id'] = $appointment['google_added_by_id'];
$newAppointmentData['outlook_event_id'] = $appointment['outlook_event_id'];
$newAppointmentData['outlook_calendar_link'] = $appointment['outlook_calendar_link'];
$newAppointmentData['outlook_added_by_id'] = $appointment['outlook_added_by_id'];
// Basic appointment details (preserved from original)
$newAppointmentData['subject'] = $appointment['subject'];
$newAppointmentData['description'] = $appointment['description'];
$newAppointmentData['email'] = $appointment['email'];
$newAppointmentData['name'] = $appointment['name'];
$newAppointmentData['phone'] = $appointment['phone'];
$newAppointmentData['address'] = $appointment['address'];
$newAppointmentData['notes'] = $appointment['notes'];
$newAppointmentData['contact_id'] = $appointment['contact_id'];
// Notification settings (preserved from original)
$newAppointmentData['by_sms'] = $appointment['by_sms'];
$newAppointmentData['by_email'] = $appointment['by_email'];
// Time and scheduling fields
$newAppointmentData['hash'] = app_generate_hash();
$newAppointmentData['start_hour'] = $appointment['start_hour'];
$newAppointmentData['end_hour'] = $appointment['end_hour'];
$newAppointmentData['duration'] = $appointment['duration'];
$newAppointmentData['timezone'] = $appointment['timezone'];
// Service and provider
$newAppointmentData['service_id'] = $appointment['service_id'];
$newAppointmentData['provider_id'] = $appointment['provider_id'];
// Status and tracking
$newAppointmentData['status'] = 'in-progress';
$newAppointmentData['created_by'] = $appointment['created_by'];
// Reminder settings (preserved from original)
$newAppointmentData['reminder_before'] = $appointment['reminder_before'];
$newAppointmentData['reminder_before_type'] = $appointment['reminder_before_type'];
// Additional fields
$newAppointmentData['cancel_notes'] = $appointment['cancel_notes'];
$newAppointmentData['source'] = $appointment['source'];
$newAppointmentData['feedback_comment'] = $appointment['feedback_comment'];
$newAppointmentData['files'] = $appointment['files'];
// Reset fields for new appointment
$newAppointmentData['notification_date'] = null;
$newAppointmentData['external_notification_date'] = null;
$newAppointmentData['feedback'] = null;
$newAppointmentData['invoice_id'] = null;
$newAppointmentData['invoice_date'] = null;
// Recurring fields (reset - this is a generated child, not a recurring parent)
$newAppointmentData['recurring_type'] = null;
$newAppointmentData['repeat_every'] = 0;
$newAppointmentData['recurring'] = 0;
$newAppointmentData['cycles'] = 0;
$newAppointmentData['total_cycles'] = 0;
$newAppointmentData['custom_recurring'] = 0;
$newAppointmentData['last_recurring_date'] = null;
$newAppointmentData = handleDataReminderFields($newAppointmentData);
$CI->db->insert($table, $newAppointmentData);
$insert_id = $CI->db->insert_id();
if ($insert_id) {
// Get the old appointment custom field and add to the new
$fieldTo = 'appointly';
$custom_fields = get_custom_fields($fieldTo);
foreach ($custom_fields as $field) {
$value = get_custom_field_value($appointment['id'], $field['id'], $fieldTo, false);
if ($value != '') {
$CI->db->insert(db_prefix() . 'customfieldsvalues', [
'relid' => $insert_id,
'fieldid' => $field['id'],
'fieldto' => $fieldTo,
'value' => $value,
]);
}
}
// update recurring date for original appointment
$CI->db->where('id', $appointment['id']);
$CI->db->update($table, ['last_recurring_date' => $re_create_at]);
// set total_cycles +1 for original appointment
$CI->db->where('id', $appointment['id']);
$CI->db->set('total_cycles', 'total_cycles+1', false);
$CI->db->update($table);
$googleAttendees = [];
// insert attendees for new appointment
$originalAttendees = $CI->db->where('appointment_id', $appointment['id'])
->get($tableAttendees)->result_array();
foreach ($originalAttendees as &$attendee) {
$googleAttendees[] = $attendee['staff_id'];
$attendee['appointment_id'] = $insert_id;
}
$CI->db->insert_batch($tableAttendees, $originalAttendees);
// Copy service relationships from original appointment
$originalServices = $CI->db->where('appointment_id', $appointment['id'])
->get(db_prefix() . 'appointly_appointment_services')->result_array();
if (!empty($originalServices)) {
foreach ($originalServices as &$service) {
$service['appointment_id'] = $insert_id;
}
$CI->db->insert_batch(db_prefix() . 'appointly_appointment_services', $originalServices);
}
// google calendar
if ($appointment['google_event_id'] != '') {
$CI->load->model('appointly/appointly_model');
$lastInsertedAppointment = $CI->db->where('id', $insert_id)->get($table)->row_array();
$googleInsertData = $CI->appointly_model->recurringAddGoogleNewEvent(
$lastInsertedAppointment,
$googleAttendees
);
if (! empty($googleInsertData)) {
// update appointment wih new google event data
$CI->db->where('id', $insert_id);
$CI->db->update($table, $googleInsertData);
}
}
newRecurringAppointmentNotifications($insert_id);
foreach ($googleAttendees as $googleAttendee) {
add_notification([
'description' => 'appointment_recurring_re_created',
'touserid' => $googleAttendee['staff_id'],
'fromcompany' => true,
'link' => 'appointly/appointments/view?appointment_id=' . $insert_id,
]);
pusher_trigger_notification([$googleAttendee['staff_id']]);
}
}
}
}
}
hooks()->add_filter('sms_gateway_available_triggers', 'appointly_register_sms_triggers');
/**
* Register SMS Triggers for appointly.
*
* @param [array] $triggers
*
* @return array
*/
function appointly_register_sms_triggers($triggers)
{
// Enhanced merge fields for comprehensive SMS notifications
$enhanced_merge_fields = [
'{appointment_subject}',
'{appointment_date}',
'{appointment_client_name}',
'{appointment_location}',
'{appointment_provider_name}',
'{appointment_description}',
'{appointment_public_url}',
'{appointment_client_phone}',
'{appointment_client_email}',
'{appointment_google_meet_link}',
];
$triggers[APPOINTLY_SMS_APPOINTMENT_APPROVED_TO_CLIENT] = [
'merge_fields' => $enhanced_merge_fields,
'label' => 'Appointment approved (Sent to Contact)',
'info' => 'Trigger when appointment is approved, SMS will be sent to the appointment contact number.',
];
$triggers[APPOINTLY_SMS_APPOINTMENT_CANCELLED_TO_CLIENT] = [
'merge_fields' => array_merge($enhanced_merge_fields, [
'{appointment_cancel_notes}',
]),
'label' => 'Appointment cancelled (Sent to Contact)',
'info' => 'Trigger when appointment is cancelled, SMS will be sent to the appointment contact number.',
];
$triggers[APPOINTLY_SMS_APPOINTMENT_APPOINTMENT_REMINDER_TO_CLIENT] = [
'merge_fields' => $enhanced_merge_fields,
'label' => 'Appointment reminder (Sent to Contact)',
'info' => 'Trigger when reminder before date is set when appointment is created, SMS will be sent to the appointment contact number.',
];
$triggers[APPOINTLY_SMS_APPOINTMENT_UPDATED_TO_CLIENT] = [
'merge_fields' => $enhanced_merge_fields,
'label' => 'Appointment updated (Sent to Contact)',
'info' => 'Trigger when appointment is updated, SMS will be sent to the appointment contact number.',
];
return $triggers;
}
/*
* Check if can have permissions then apply new tab in settings
*/
hooks()->add_action('admin_init', 'appointly_add_settings_section');
function appointly_add_settings_section()
{
$CI = &get_instance();
// Message: app_tabs->add_settings_tab is deprecated since version 3.2.0! Use app->add_settings_section instead.
if (version_compare(get_app_version(), '3.2.0', '<')) {
$CI->app_tabs->add_settings_tab('appointly-settings', [
'name' => _l('appointment_appointments'),
'view' => 'appointly/appointly_settings',
'position' => 36,
]);
} else {
$CI->app->add_settings_section('appointly-settings', [
'title' => _l('appointly_module_name'),
'position' => 36,
'children' => [
[
'name' => _l('appointment_appointments'),
'view' => 'appointly/appointly_settings',
'icon' => 'fa-regular fa-calendar fa-fw fa-lg',
'position' => 10,
],
],
]);
}
}
/*
* Need to change encode array values to string for database before post
* Intercepting settings-form
*/
hooks()->add_filter('before_settings_updated', 'modify_settings_form_post');
function modify_settings_form_post($form)
{
if (isset($form['settings']['appointly_default_feedbacks'])) {
$form['settings']['appointly_default_feedbacks'] = json_encode(
$form['settings']['appointly_default_feedbacks']
);
if ($form['settings']['appointly_default_feedbacks'] == null) {
$form['settings']['appointly_default_feedbacks'] = json_encode([]);
}
}
return $form;
}
if (! function_exists('appointly_add_filters_js')) {
function appointly_add_filters_js()
{
// Only load on appointment pages
$CI = &get_instance();
// Load core script for debugging control
echo '<script src="' . module_dir_url(APPOINTLY_MODULE_NAME, 'assets/js/appointly-core.js') . '?v=' . time() . '"></script>';
// Load enhanced calendar tooltips on dashboard and calendar pages
if (
strpos($CI->uri->uri_string(), 'admin') !== false ||
strpos($CI->uri->uri_string(), 'admin/utilities/calendar') !== false ||
strpos($CI->uri->uri_string(), 'appointly/appointments') !== false
) {
// Load tooltip CSS
echo '<link rel="stylesheet" href="' . module_dir_url(APPOINTLY_MODULE_NAME, 'assets/css/appointly_calendar_tooltips.css') . '?v=' . time() . '">';
// Load tooltip JavaScript
echo '<script src="' . module_dir_url(APPOINTLY_MODULE_NAME, 'assets/js/appointly_calendar_tooltips.js') . '?v=' . time() . '"></script>';
}
if (strpos($CI->uri->uri_string(), 'appointly/appointments') !== false) {
echo '<script src="' . module_dir_url(APPOINTLY_MODULE_NAME, 'assets/js/appointly_filters.js') . '?v=' . time() . '"></script>';
}
}
}
// Lead tab hooks
hooks()->add_action('after_lead_lead_tabs', 'appointly_add_appointment_tab_to_lead');
hooks()->add_action('after_lead_tabs_content', 'appointly_add_appointment_content_to_lead');
function appointly_add_appointment_tab_to_lead($lead)
{
?>
<li role="presentation">
<a href="#lead_appointments" aria-controls="lead_appointments" role="tab" data-toggle="tab">
<i class="fa-regular fa-calendar"></i>
<?php
echo _l('appointment_appointments'); ?>
</a>
</li>
<?php
}
function appointly_add_appointment_content_to_lead($lead)
{
?>
<div role="tabpanel" class="tab-pane" id="lead_appointments">
<?php
$CI = &get_instance();
$CI->load->model('appointly/appointly_model');
$appointments = $CI->appointly_model->get_lead_appointments($lead->id);
$CI->load->view('appointly/lead_appointments', ['appointments' => $appointments, 'lead' => $lead]);
?>
</div>
<?php
}
/*
* Add appointments tab to client profile
*/
hooks()->add_action('admin_init', 'appointly_add_appointments_tab_to_client_profile');
function appointly_add_appointments_tab_to_client_profile()
{
$CI = &get_instance();
$CI->app_tabs->add_customer_profile_tab('appointments', [
'name' => _l('appointment_appointments'),
'icon' => 'fa-regular fa-calendar',
'view' => 'appointly/client_appointments_tab',
'position' => 6,
]);
}
/**
* Send Pusher notification when appointment invoice is paid
*/
hooks()->add_action('after_payment_added', function ($payment_id) {
$CI = &get_instance();
$payment = $CI->payments_model->get($payment_id);
// Check if payment is for an appointment invoice
$CI->db->select('provider_id, subject');
$CI->db->where('invoice_id', $payment->invoiceid);
$appointment = $CI->db->get(db_prefix() . 'appointly_appointments')->row();
if ($appointment && $appointment->provider_id) {
add_notification([
'description' => _l('payment_received_for_appointment') . ' ' . $appointment->subject,
'touserid' => $appointment->provider_id,
'fromcompany' => true,
'link' => 'appointly/appointments/view?appointment_id=' . $appointment->id,
]);
pusher_trigger_notification([$appointment->provider_id]);
}
});
/**
* Add appointly email templates to the admin email templates page
*
* @return void
*/
if (! function_exists('appointly_add_email_templates')) {
function appointly_add_email_templates()
{
$CI = &get_instance();
$data['appointly_templates'] = $CI->emails_model->get(['type' => 'appointly', 'language' => 'english']);
$data['hasPermissionEdit'] = staff_can('edit', 'email_templates');
$CI->load->view('appointly/email_templates', $data);
}
}
/**
* Add appointments permission to client contact permissions
*
* @param array $permissions
* @return array
*/
function appointly_add_contact_permission($permissions)
{
$maxId = 0;
foreach ($permissions as $perm) {
if (isset($perm['id']) && is_numeric($perm['id'])) {
$id = (int) $perm['id'];
if ($id > $maxId) $maxId = $id;
}
}
$permissions[] = [
'id' => $maxId + 1,
'name' => _l('customer_permission_appointments'),
'short_name' => 'appointments',
];
return $permissions;
}