widgets = new AIO_Time_Clock_Lite_Widgets(); add_action('widgets_init', [$this, 'loadWidgets']); } add_action('init', [$this, 'maybeHandleRoles']); } public function getVersion() { return "2.1.0"; } /** * Rebuild the plugin roles only when they are out of date. * * handleRoles() removes and re-adds every role, and each of those writes the * wp_user_roles option, so running it on every request meant eight option * writes per page load. The capabilities only change when the plugin does, so * the stored version is enough to decide. The get_role() check is a safety net * that rebuilds the roles if something else removed them between updates; it * reads the roles already held in memory and costs no extra query. */ public function maybeHandleRoles() { if (get_option('aio_time_clock_lite_roles_version') === $this->getVersion() && get_role('time_clock_admin')) { return; } $this->handleRoles(); update_option('aio_time_clock_lite_roles_version', $this->getVersion()); } /** * Read the changelog bullets for a version out of readme.txt. * * readme.txt ships inside the plugin folder, so the update news page can show * what actually changed in this release instead of a hardcoded list that goes * stale the moment the next version ships. * * @param string|null $version Version to look up. Defaults to the running one. * @return string[] The bullet text, numbering stripped. Empty if not found. */ public function getChangelogEntries($version = null) { $version = ($version === null) ? $this->getVersion() : $version; $readme = plugin_dir_path(__FILE__) . 'readme.txt'; if (! is_readable($readme)) { return []; } $contents = file_get_contents($readme); if ($contents === false || $contents === '') { return []; } // Entries read "2026-8-23 - Version 2.1.0" followed by numbered lines. The // trailing anchor keeps "2.1.0" from also matching inside "2.1.01". $pattern = '/^[^\r\n]*-[ \t]*Version[ \t]+' . preg_quote($version, '/') . '[ \t\r]*$(.*?)(?=^[^\r\n]*-[ \t]*Version[ \t]|\z)/ms'; if (! preg_match($pattern, $contents, $match)) { return []; } $entries = []; foreach (preg_split('/\r\n|\r|\n/', $match[1]) as $line) { $line = trim($line); if ($line === '') { continue; } $entries[] = preg_replace('/^\d+\.[ \t]*/', '', $line); } return $entries; } public function handleRoles() { remove_role('manager'); $result = add_role( 'manager', esc_attr_x('Manager', 'aio-time-clock-lite', 'aio-time-clock-lite'), [ 'read' => true, 'create_posts' => true, 'edit_posts' => true, 'edit_others_posts' => true, 'publish_posts' => true, 'manage_categories' => true, ] ); remove_role('volunteer'); $result = add_role( 'volunteer', esc_attr_x('Volunteer', 'aio-time-clock-lite', 'aio-time-clock-lite'), [ 'read' => true, ] ); remove_role('employee'); $result = add_role( 'employee', esc_attr_x('Employee', 'aio-time-clock-lite', 'aio-time-clock-lite'), [ 'read' => true, ] ); remove_role('time_clock_admin'); $result = add_role( 'time_clock_admin', esc_attr_x('Time Clock Admin', 'aio-time-clock-lite', 'aio-time-clock-lite'), [ 'read' => true, // Allows a user to read 'create_shifts' => true, // Allows user to create new posts 'edit_posts' => true, // Allows user to edit their own posts 'edit_shifts' => true, // Allows user to edit their own posts 'edit_others_posts' => true, // Allows user to edit others posts too 'edit_others_shifts' => true, // Allows user to edit others posts too 'publish_posts' => true, // Allows the user to publish posts 'publish_shifts' => true, // Allows the user to publish posts 'manage_categories' => true, // Allows user to manage post categories 'edit_private_posts' => true, // Allows user to manage post categories 'edit_private_shifts' => true, // Allows user to manage post categories 'read_private_posts' => true, // Allows user to manage post categories 'read_private_shifts' => true, // Allows user to manage post categories 'edit_published_posts' => true, // Allows user to manage post categories 'edit_published_shifts' => true, // Allows user to manage post categories ] ); } public function pluginInitLite() { load_plugin_textdomain('aio-time-clock-lite', false, dirname(plugin_basename(__FILE__)) . '/languages/'); } public function addMenuItem() { $page_hook_suffix = add_menu_page('Time Clock Lite', 'Time Clock Lite', 'edit_posts', 'aio-tc-lite', [$this, 'settingsPageLite'], 'dashicons-clock'); add_submenu_page('aio-tc-lite', esc_attr_x('Settings', 'aio-time-clock-lite', 'aio-time-clock-lite'), esc_attr_x('Settings', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_posts', 'aio-tc-lite', [$this, 'settingsPageLite']); add_submenu_page('aio-tc-lite', esc_attr_x('Real Time Monitoring', 'aio-time-clock-lite', 'aio-time-clock-lite'), esc_attr_x('Real Time Monitoring', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_posts', 'aio-monitoring-sub', [$this, 'montioringPage']); add_submenu_page('aio-tc-lite', esc_attr_x('Employees', 'aio-time-clock-lite', 'aio-time-clock-lite'), esc_attr_x('Employees', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_posts', 'aio-employees-sub', [$this, 'employeePage']); add_submenu_page('aio-tc-lite', esc_attr_x('Departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), esc_attr_x('Departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_posts', 'aio-department-sub', [$this, 'departmentPage']); add_submenu_page('aio-tc-lite', esc_attr_x('Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), esc_attr_x('Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_posts', 'aio-shifts-sub', [$this, 'shiftsPage']); add_submenu_page('aio-tc-lite', esc_attr_x('Reports', 'aio-time-clock-lite', 'aio-time-clock-lite'), esc_attr_x('Reports', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_posts', 'aio-reports-sub', [$this, 'reportsPageLite']); } public function frontEndScripts() { if (! is_user_logged_in()) { return; } //Styles wp_register_style('datetimepicker-style', plugins_url('css/jquery.datetimepicker.css', __FILE__)); wp_register_style('aio-tc-site-style', plugins_url('css/aio-site.css', __FILE__), [], $this->getVersion()); wp_enqueue_style('datetimepicker-style'); wp_enqueue_style('aio-tc-site-style'); wp_register_style('aio_time_clock_lite_toastr_style', plugins_url('css/toastr.min.css', __FILE__)); wp_enqueue_style('aio_time_clock_lite_toastr_style'); wp_register_style('aio_time_clock_lite_swal_style', plugins_url('css/sweetalert2.min.css', __FILE__)); wp_enqueue_style('aio_time_clock_lite_swal_style'); //Scripts wp_enqueue_script('jquery'); wp_enqueue_script('jquery-effects-core'); wp_enqueue_script('jquery-ui-core'); wp_enqueue_script('aio_time_clock_lite_js', plugins_url('/js/time-clock-lite.js', __FILE__), ['jquery'], $this->getVersion()); wp_enqueue_script('nert-aio-timepicker', plugins_url('js/jquery.datetimepicker.js', __FILE__), ['jquery']); wp_enqueue_script('aio_time_clock_lite_js'); wp_localize_script('aio_time_clock_lite_js', 'timeClockAjax', $this->getTranslationStrings('front')); wp_register_style('aio_time_clock_lite_toastr_style', plugins_url('css/toastr.min.css', __FILE__)); wp_enqueue_style('aio_time_clock_lite_toastr_style'); wp_enqueue_script('aio_time_clock_lite_swal_js', plugins_url('js/sweetalert2.min.js', __FILE__), ['jquery', 'jquery-effects-core', 'jquery-ui-core'], $this->getVersion()); } public function adminScripts() { //Styles wp_register_style('datetimepicker-style', plugins_url('css/jquery.datetimepicker.css', __FILE__)); wp_enqueue_style('datetimepicker-style'); wp_register_style('aio-tc-admin-style', plugins_url('css/aio-admin.css', __FILE__), [], $this->getVersion()); wp_register_style('aio-tc-lite-steps-style', plugins_url('css/jquery.steps.css', __FILE__)); wp_enqueue_style('aio-tc-admin-style'); wp_enqueue_style('aio-tc-lite-steps-style'); wp_register_style('aio_time_clock_front_toastr_style', plugins_url('css/toastr.min.css', __FILE__)); wp_enqueue_style('aio_time_clock_front_toastr_style'); wp_register_style('aio_time_clock_swal_front_style', plugins_url('css/sweetalert2.min.css', __FILE__)); wp_enqueue_style('aio_time_clock_swal_front_style'); //Scripts wp_enqueue_script('jquery'); wp_enqueue_script('jquery-effects-core'); wp_enqueue_script('jquery-ui-core'); wp_enqueue_script('aio_time_clock_front_toastr_js', plugins_url('js/toastr.min.js', __FILE__), ['jquery', 'jquery-effects-core', 'jquery-ui-core'], $this->getVersion()); wp_enqueue_script('aio_time_clock_swal_js', plugins_url('js/sweetalert2.min.js', __FILE__), ['jquery', 'jquery-effects-core', 'jquery-ui-core'], $this->getVersion()); wp_register_script("aio_time_clock_lite_steps", plugins_url('/js/jquery.steps.min.js', __FILE__), ['jquery']); wp_enqueue_script('aio_time_clock_lite_admin_js', plugins_url('/js/time-clock-lite-admin.js', __FILE__), ['jquery'], $this->getVersion()); wp_enqueue_script('nert-aio-timepicker', plugins_url('js/jquery.datetimepicker.js', __FILE__), ['jquery']); wp_enqueue_script('aio_time_clock_lite_admin_js'); wp_enqueue_script('aio_time_clock_lite_steps'); if (current_user_can('edit_posts')) { wp_localize_script('aio_time_clock_lite_admin_js', 'timeClockAdminAjax', $this->getTranslationStrings('admin')); } } public function aio_time_clock_lite_plugin_update() { add_option('aio_time_clock_lite_update_redirect', true); } public function aio_timeclock_lite_plugin_redirect() { if (get_option('aio_time_clock_lite_update_redirect', false)) { delete_option('aio_time_clock_lite_update_redirect'); wp_safe_redirect(admin_url('admin.php?page=aio-tc-lite&tab=news')); exit; } } public function remove_my_post_metaboxes_aio_lite() { remove_meta_box('authordiv', 'shift', 'normal'); remove_meta_box('commentstatusdiv', 'shift', 'normal'); remove_meta_box('commentsdiv', 'shift', 'normal'); } private function is_edit_mode() { // Gutenberg block editor or classic editor screen if (is_admin() && function_exists('get_current_screen')) { $screen = get_current_screen(); if ($screen && $screen->is_block_editor) { return true; } } // Elementor edit mode if (defined('ELEMENTOR_VERSION') && \Elementor\Plugin::$instance->editor->is_edit_mode()) { return true; } // Divi or other builders can be added here later if needed return false; } public function aio_remove_row_actions($actions) { if (get_post_type() === 'shift') { unset($actions['view']); } return $actions; } public function show_aio_time_clock_lite($atts) { if ($this->is_edit_mode()) { return '
' . esc_html__('[Time Clock Placeholder – Not visible in editor]', 'aio-time-clock-lite') . '
'; } $tc_page = $this->aio_check_tc_shortcode_lite(); $nonce = wp_create_nonce("aio_tcl_front"); $link = admin_url('admin-ajax.php?action=aio_tcl_front&post_id=' . get_the_ID() . '&nonce=' . esc_attr($nonce)); ob_start(); require_once "templates/time-clock-style1.php"; return ob_get_clean(); } public function show_aio_employee_profile_lite($atts) { if ($this->is_edit_mode()) { return '
' . esc_html__('[Employee Profile Placeholder – Not visible in editor]', 'aio-time-clock-lite') . '
'; } $ep_page = $this->check_eprofile_shortcode_lite(); $nonce = wp_create_nonce("aio_tcl_front"); $link = admin_url('admin-ajax.php?action=aio_tcl_front&post_id=' . get_the_ID() . '&nonce=' . esc_attr($nonce)); ob_start(); require_once "aio-employee-profile.php"; return ob_get_clean(); } public function aio_time_clock_lite_js() { ob_clean(); header('Content-Type: application/json; charset=utf-8'); $this->require_auth_front(); $this->check_ajax_nonce('aio_tcl_front', 'nonce'); global $current_user; $clock_action = (isset($_POST["clock_action"])) ? sanitize_text_field($_POST["clock_action"]) : null; $employee_clock_in_time = null; $employee_clock_out_time = null; $is_clocked_in = false; $open_shift_id = (isset($_POST["open_shift_id"])) ? intval($_POST["open_shift_id"]) : null; $new_shift_created = false; $employee = get_current_user_id(); $nonce = isset($_POST["nonce"]) ? sanitize_text_field($_POST["nonce"]) : null; $message = ""; $response_html = ""; $time_total = strtotime(0); if ($clock_action == "check_shifts") { $found_shift_id = null; $args = [ 'post_type' => 'shift', 'orderby' => 'ID', 'author' => get_current_user_id() ]; $query = new WP_Query($args); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $custom = get_post_custom($query->post->ID); $author_id = get_post_field('post_author', $query->post->ID); $employee_clock_in_time = (isset($custom["employee_clock_in_time"][0])) ? sanitize_text_field($custom["employee_clock_in_time"][0]) : null; $employee_clock_out_time = (isset($custom["employee_clock_out_time"][0])) ? sanitize_text_field($custom["employee_clock_out_time"][0]) : null; if ($employee_clock_in_time != null && $employee_clock_out_time == null && $current_user->ID == $author_id) { $found_shift_id = $query->post->ID; $is_clocked_in = true; break; } } } echo json_encode( [ "response" => "success", "message" => $message, "employee" => $employee, "clock_action" => $clock_action, "is_clocked_in" => $is_clocked_in, "open_shift_id" => $found_shift_id, "employee_clock_in_time" => (($employee_clock_in_time != null) ? $this->cleanDate($employee_clock_in_time) : null), "employee_clock_out_time" => (($employee_clock_in_time != null) ? $this->cleanDate($employee_clock_out_time) : null), ] ); } elseif ($clock_action == "clock_in") { $device_time = (isset($_POST["device_time"])) ? sanitize_text_field($_POST["device_time"]) : null; $current_time = $this->getCurrentTime(); if ($employee == get_current_user_id()) { $aio_new_shift = [ 'post_type' => 'shift', 'post_title' => 'Employee Shift', 'post_status' => 'publish', 'post_author' => get_current_user_id(), ]; $new_post_id = wp_insert_post($aio_new_shift); $department = ""; $terms = get_the_terms($current_user->ID, 'department'); if (! empty($terms)) { foreach ($terms as $term) { $department = $term->name; } } update_post_meta($new_post_id, 'employee_clock_in_time', sanitize_text_field($current_time["current_time"])); update_post_meta($new_post_id, 'employee_clock_out_time', null); if ($department != null) { add_post_meta($new_post_id, 'department', $department, true); } add_post_meta($new_post_id, 'ip_address_in', sanitize_text_field($_SERVER['REMOTE_ADDR']), true); $open_shift_id = $new_post_id; $is_clocked_in = true; echo json_encode( [ "response" => "success", "message" => $message, "employee" => $employee, "open_shift_id" => $open_shift_id, "clock_action" => $clock_action, "is_clocked_in" => $is_clocked_in, "employee_clock_in_time" => $this->cleanDate(sanitize_text_field($current_time["current_time"])), "employee_clock_out_time" => null, ] ); } else { echo json_encode( [ "response" => "failed", "message" => esc_attr_x("You cannot clock in as another employee", 'aio-time-clock-lite', 'aio-time-clock-lite'), ] ); } } elseif ($clock_action == "clock_out") { $device_time = isset($_POST["device_time"]) ? sanitize_text_field($_POST["device_time"]) : null; $current_time = $this->getCurrentTime(); $author_id = get_post_field('post_author', $open_shift_id); if ($author_id == get_current_user_id()) { $is_clocked_in = false; $employee_clock_in_time = get_post_meta($open_shift_id, 'employee_clock_in_time', true); update_post_meta($open_shift_id, 'employee_clock_out_time', sanitize_text_field($current_time["current_time"])); add_post_meta($open_shift_id, 'ip_address_out', sanitize_text_field($_SERVER['REMOTE_ADDR']), true); $shift_sum = $this->dateDifference($employee_clock_in_time, sanitize_text_field($current_time["current_time"])); $time_total = $shift_sum; echo json_encode( [ "response" => "success", "message" => $message, "employee" => $author_id, "clock_action" => $clock_action, "employee_clock_in_time" => $this->cleanDate($employee_clock_in_time), "employee_clock_out_time" => $this->cleanDate(sanitize_text_field($current_time["current_time"])), "time_total" => $this->secondsToTime($time_total), "is_clocked_in" => $is_clocked_in, ] ); } else { echo json_encode( [ "response" => "failed", "message" => esc_attr_x("You cannot clock out of another employee\'s shift", 'aio-time-clock-lite', 'aio-time-clock-lite'), ] ); } } else { echo json_encode( [ "response" => "failed", "message" => esc_attr_x("action does not exist", 'aio-time-clock-lite', 'aio-time-clock-lite'), "employee" => $current_user->ID, "clock_action" => $clock_action, ] ); } wp_reset_postdata(); die(); } public function secondsToTime($seconds) { if ($seconds != null) { $dtF = new DateTime('@0'); $dtT = new DateTime("@$seconds"); $days = $dtF->diff($dtT)->format('%D'); $hours = $dtF->diff($dtT)->format('%H'); $minutes = $dtF->diff($dtT)->format('%I'); $hours = $hours + ($days * 24); return $hours . ":" . $minutes; } else { return '00:00'; } } public function aio_time_clock_lite_admin_js() { ob_clean(); // clear any previous output header('Content-Type: application/json; charset=utf-8'); $this->require_auth_manage(); $this->check_ajax_nonce('aio_tcl_admin', 'nonce'); global $current_user; $admin_action = isset($_POST["admin_action"]) ? sanitize_text_field($_POST["admin_action"]) : null; $report_action = isset($_POST["report_action"]) ? sanitize_text_field($_POST["report_action"]) : null; $nonce = isset($_POST["nonce"]) ? sanitize_text_field($_POST["nonce"]) : null; $message = ""; $response_html = ""; $employee = isset($_POST["employee"]) ? absint($_POST["employee"]) : get_current_user_id(); $date_range_start = isset($_POST["aio_pp_start_date"]) ? date($this->mysqlDateFormat, strtotime(sanitize_text_field($_POST["aio_pp_start_date"]))) : null; $date_range_end = isset($_POST["aio_pp_end_date"]) ? date($this->mysqlDateFormat, strtotime(sanitize_text_field($_POST["aio_pp_end_date"]))) : null; $errors = null; // Normalize dates & validate range (optional but recommended) $drs = $date_range_start ? date($this->mysqlDateFormat, strtotime($date_range_start)) : null; $dre = $date_range_end ? date($this->mysqlDateFormat, strtotime($date_range_end)) : null; if ($admin_action === 'report') { if (! $drs || ! $dre || strtotime($drs) === false || strtotime($dre) === false || strtotime($drs) > strtotime($dre)) { wp_send_json_error(['message' => 'Invalid date range'], 400); } } $response = []; // initialize base response if ($admin_action == "create_timeclock_page") { $tc_page = $this->aio_check_tc_shortcode_lite(); $new_page_id = null; if ($tc_page == null) { $my_post = [ 'post_type' => 'page', 'post_title' => 'Time Clock', 'post_status' => 'publish', 'post_content' => '[show_aio_time_clock_lite]', 'comment_status' => 'closed', 'post_author' => get_current_user_id(), ]; $new_page_id = wp_insert_post($my_post); } if ($new_page_id) { $message = esc_attr_x('Time Clock page created successfully.', 'aio-time-clock-lite', 'aio-time-clock-lite'); $response = [ "response" => "success", "message" => $message, "nonce" => $nonce, "admin_action" => $admin_action, "page_id" => $new_page_id, "link" => esc_url(get_permalink($new_page_id)), ]; } elseif ($tc_page) { $message = esc_attr_x('You already have a Time Clock page created.', 'aio-time-clock-lite', 'aio-time-clock-lite'); $response = [ "response" => "failed", "message" => $message, "nonce" => $nonce, "admin_action" => $admin_action, "link" => esc_url(get_permalink($tc_page)), ]; } else { $message = esc_attr_x('Unable to create Time Clock page.', 'aio-time-clock-lite', 'aio-time-clock-lite'); $response = [ "response" => "failed", "message" => $message, "nonce" => $nonce, "admin_action" => $admin_action, ]; } } elseif ($admin_action == "create_eprofile_page") { $eprofile_page = $this->check_eprofile_shortcode_lite(); $new_eprofile_id = null; if ($eprofile_page == null) { $my_post = [ 'post_type' => 'page', 'post_title' => 'Employee Profile', 'post_status' => 'publish', 'post_content' => '[show_aio_employee_profile_lite]', 'comment_status' => 'closed', 'post_author' => get_current_user_id(), ]; $new_eprofile_id = wp_insert_post($my_post); } if ($new_eprofile_id) { $message = esc_attr_x('Employee Profile page created successfully.', 'aio-time-clock-lite', 'aio-time-clock-lite'); $response = [ "response" => "success", "message" => $message, "nonce" => $nonce, "admin_action" => $admin_action, "page_id" => $new_eprofile_id, "link" => esc_url(get_permalink($new_eprofile_id)), ]; } elseif ($eprofile_page) { $message = esc_attr_x('You already have an Employee Profile page created.', 'aio-time-clock-lite', 'aio-time-clock-lite'); $response = [ "response" => "failed", "message" => $message, "nonce" => $nonce, "admin_action" => $admin_action, "page_id" => $eprofile_page, "link" => esc_url(get_permalink($eprofile_page)), ]; } else { $message = esc_attr_x('Unable to create Employee Profile page.', 'aio-time-clock-lite', 'aio-time-clock-lite'); $response = [ "response" => "failed", "message" => $message, "nonce" => $nonce, "admin_action" => $admin_action, ]; } } elseif ($admin_action == "report") { if ($report_action) { $response = [ "response" => "success", "employee" => $employee, "date_range_start" => $drs, "date_range_end" => $dre, "report_action" => $report_action, "shifts" => $this->getShiftTotalFromRange($employee, $drs, $dre), ]; } else { $response = [ "response" => "failed", "employee" => $employee, "date_range_start" => $drs, "date_range_end" => $dre, "message" => esc_attr_x("Report action cannot be null", "aio-time-clock-lite", 'aio-time-clock-lite'), "errors" => $errors, ]; } } else { $response = [ "response" => "failed", "message" => esc_attr_x("Action does not exist", 'aio-time-clock-lite', 'aio-time-clock-lite'), "employee" => $employee, "admin_action" => $admin_action, ]; } ob_end_clean(); // discard anything accidentally printed header('Content-Type: application/json; charset=utf-8'); echo wp_json_encode($response); wp_die(); } public function aio_tc_custom_post_shift_lite() { $labels = [ 'name' => esc_attr_x('Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'singular_name' => esc_attr_x('Shift', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'add_new' => esc_attr_x('Add New', 'shift', 'aio-time-clock-lite'), 'add_new_item' => esc_attr_x('Clock Out', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_item' => esc_attr_x('Edit Shift', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'new_item' => esc_attr_x('Clock In', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'all_items' => esc_attr_x('All Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'view_item' => esc_attr_x('View Shift', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'search_items' => esc_attr_x('Search Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'not_found' => esc_attr_x('No shifts found', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'not_found_in_trash' => esc_attr_x('No shifts found in the Trash', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'parent_item_colon' => '', 'menu_name' => esc_attr_x('Employee Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), ]; $args = [ 'labels' => $labels, 'description' => esc_attr_x('Employee shifts dates and times', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'query_var' => true, 'public' => false, 'show_ui' => true, 'supports' => ['title', 'author'], 'has_archive' => true, 'show_tagcloud' => false, 'rewrite' => ['slug' => 'shifts'], 'show_in_nav_menus' => false, ]; register_post_type('shift', $args); } public function aio_timeclock_admin_init_lite() { add_filter('manage_edit-shift_columns', [$this, 'shiftColumnsFilter'], 10, 1); add_action('manage_shift_posts_custom_column', [$this, 'shiftColumn'], 10, 2); } public function shiftColumnsFilter($columns) { unset($columns['date']); unset($columns['author']); $columns['employee'] = esc_attr_x('Employee', 'aio-time-clock-lite', 'aio-time-clock-lite'); $columns['department'] = esc_attr_x('Department', 'aio-time-clock-lite', 'aio-time-clock-lite'); $columns['employee_clock_in_time'] = esc_attr_x('Clock In Time', 'aio-time-clock-lite', 'aio-time-clock-lite'); $columns['employee_clock_out_time'] = esc_attr_x('Clock Out Time', 'aio-time-clock-lite', 'aio-time-clock-lite'); $columns['total_shift_time'] = esc_attr_x('Total Time', 'aio-time-clock-lite', 'aio-time-clock-lite'); return $columns; } public function shiftColumn($column, $post_id) { global $post; $custom = get_post_custom($post_id); $employee_clock_in_time = isset($custom['employee_clock_in_time'][0]) ? $this->cleanDate(sanitize_text_field($custom['employee_clock_in_time'][0])) : null; $employee_clock_out_time = isset($custom['employee_clock_out_time'][0]) ? $this->cleanDate(sanitize_text_field($custom['employee_clock_out_time'][0])) : null; $shift_sum = "00:00"; if ($employee_clock_in_time != null && $employee_clock_out_time != null) { $shift_sum = $this->secondsToTime($this->dateDifference($employee_clock_in_time, $employee_clock_out_time)); } $author_id = get_the_author_meta('ID'); switch ($column) { case 'employee': echo esc_attr($this->getEmployeeName($author_id)); break; case 'department': echo esc_attr($this->getDepartmentColumn($author_id)); break; case 'employee_clock_in_time': echo esc_attr($employee_clock_in_time); break; case 'employee_clock_out_time': echo esc_attr($employee_clock_out_time); break; case 'total_shift_time': echo esc_attr($shift_sum); break; } } public function getDepartmentColumn($author_id) { $department = ""; global $user; global $wordpress; $terms = get_terms('department', ['hide_empty' => false]); if (! empty($terms)) { foreach ($terms as $term) { if (is_object_in_term(intval($author_id), 'department', $term)) { $department .= sanitize_text_field($term->name) . " "; } } } return $department; } public function getShiftTotal($post_id) { $employee_clock_in_time = get_post_meta($post_id, 'employee_clock_in_time', true); $employee_clock_out_time = get_post_meta($post_id, 'employee_clock_out_time', true); $total_shift_time = strtotime(0); if ($employee_clock_in_time != null && $employee_clock_out_time != null) { if (strtotime($employee_clock_in_time) > strtotime(0) && strtotime($employee_clock_out_time) > strtotime(0)) { $total_shift_time = $this->dateDifference($employee_clock_in_time, $employee_clock_out_time); } } return $total_shift_time; } public function aio_shift_info_box_meta_lite() { add_meta_box( 'shift_info_box', esc_attr_x('Shift Info', 'aio-time-clock-lite', 'aio-time-clock-lite'), [$this, 'aio_shift_info_box_content'], 'shift', 'normal', 'high' ); } public function aio_shift_info_box_content() { require_once "aio-time-clock-box-content.php"; } public function getEmployeeSelect($selected = null) { $selected = json_decode($selected); $count = 0; $users = $this->getUsers(); foreach ($users as $user) { $active = ""; if ($selected == $user["employee_id"]) { $active = "selected"; } $user_id = isset($user["employee_id"]) ? intval($user["employee_id"]) : 0; $user_first = isset($user["first_name"]) ? sanitize_text_field($user["first_name"]) : ""; $user_last = isset($user["last_name"]) ? sanitize_text_field($user["last_name"]) : ""; echo ''; $count++; } } public function getBuiltInRoles() { return ['aio_tc_employee', 'aio_tc_manager', 'time_clock_admin', 'aio_tc_volunteer', 'aio_tc_contractor', 'employee', 'manager', 'volunteer', 'contractor', 'administrator']; } public function aio_filter_roles_lite($user) { $roles = $this->getBuiltInRoles(); return array_intersect($user->roles, $roles); } public function aio_save_shift_meta_lite($post_id) { $clock_in = (isset($_REQUEST['clock_in'])) ? sanitize_text_field($_REQUEST['clock_in']) : null; $clock_out = (isset($_REQUEST['clock_out'])) ? sanitize_text_field($_REQUEST['clock_out']) : null; if ($clock_in != null) { $clock_in = str_replace("/", "-", $clock_in); update_post_meta($post_id, 'employee_clock_in_time', date($this->dateFormat, strtotime($clock_in))); } else { update_post_meta($post_id, 'employee_clock_in_time', null); } if ($clock_out != null) { $clock_out = str_replace("/", "-", $clock_out); update_post_meta($post_id, 'employee_clock_out_time', date($this->dateFormat, strtotime($clock_out))); } else { update_post_meta($post_id, 'employee_clock_out_time', null); } if (isset($_REQUEST['employee_id'])) { remove_action('save_post', [$this, 'aio_save_shift_meta_lite']); $arg = [ 'ID' => $post_id, 'post_author' => intval($_REQUEST['employee_id']), ]; wp_update_post($arg); add_action('save_post', [$this, 'aio_save_shift_meta_lite']); } } public function removeMetaBoxes() { remove_meta_box('authordiv', 'shift', 'normal'); } public function settingsPageLite() { include "aio-settings.php"; } public function montioringPage() { include "aio-monitoring.php"; } public function reportsPageLite() { include "aio-reports.php"; } public function register_aio_timeclock_lite_settings() { register_setting('nertworks-timeclock-settings-group', 'aio_company_name', ['sanitize_callback' => [$this, 'clean_text']]); register_setting('nertworks-timeclock-settings-group', 'aio_pay_schedule', ['sanitize_callback' => [$this, 'clean_text']]); register_setting('nertworks-timeclock-settings-group', 'aio_wage_manage', ['sanitize_callback' => [$this, 'clean_text']]); register_setting('nertworks-timeclock-settings-group', 'aio_timeclock_text_align', ['sanitize_callback' => [$this, 'clean_text']]); register_setting('nertworks-timeclock-settings-group', 'aio_timeclock_redirect_employees', ['sanitize_callback' => [$this, 'clean_text']]); register_setting('nertworks-timeclock-settings-group', 'aio_timeclock_show_avatar', ['sanitize_callback' => [$this, 'clean_text']]); } public function clean_text($value) { $value = str_replace('"', "", $value); return sanitize_text_field($value); } public function aio_check_tc_shortcode_lite() { $loop = new WP_Query(['post_type' => 'page', 'posts_per_page' => -1]); while ($loop->have_posts()): $loop->the_post(); $content = get_the_content(); if (has_shortcode($content, 'show_aio_time_clock_lite')) { return $loop->post->ID; break; } else { //echo "none"; } endwhile; wp_reset_query(); } public function check_eprofile_shortcode_lite() { $loop = new WP_Query(['post_type' => 'page', 'posts_per_page' => -1]); while ($loop->have_posts()): $loop->the_post(); $content = get_the_content(); if (has_shortcode($content, 'show_aio_employee_profile_lite')) { return $loop->post->ID; } else { //echo "none"; } endwhile; wp_reset_query(); } public function memberLoginRedirect($url, $request, $user) { $tc_page = $this->aio_check_tc_shortcode_lite(); $roles = $this->getBuiltInRoles(); $roles = $this->arrayDelete(['administrator'], $roles); if (isset($user->roles)) { if (is_array($user->roles) && $tc_page != null) { if (array_intersect($roles, $user->roles)) { return esc_url(get_permalink($tc_page)); } } } return $url; } public function arrayDelete($del_val, $array) { if (is_array($del_val)) { foreach ($del_val as $del_key => $del_value) { foreach ($array as $key => $value) { if ($value == $del_value) { unset($array[$key]); } } } } else { foreach ($array as $key => $value) { if ($value == $del_val) { unset($array[$key]); } } } return array_values($array); } public function shiftsPage() { wp_safe_redirect(admin_url('edit.php?post_type=shift')); echo ''; exit; } public function employeePage() { include "aio-employees.php"; } public function departmentPage() { wp_safe_redirect(admin_url('edit-tags.php?taxonomy=department')); echo ''; exit; } public function dateDifference($start, $end) { if (isset($start) && isset($end)) { if ($start != null && $end != null) { $start = sanitize_text_field($start); $end = sanitize_text_field($end); if ($this->isValidDate($start) && $this->isValidDate($end)) { $start = str_replace('/', '-', $start); $end = str_replace('/', '-', $end); $s = new DateTime(); $start_date = $s->setTimestamp(intval(strtotime($start))); $e = new DateTime(); $end_date = $e->setTimestamp(intval(strtotime($end))); $diff = $end_date->diff($start_date); $diff_sec = $diff->format('%r') . ( ($diff->s) + (60 * ($diff->i)) + (60 * 60 * ($diff->h)) + (24 * 60 * 60 * ($diff->d)) + (30 * 24 * 60 * 60 * ($diff->m)) + (365 * 24 * 60 * 60 * ($diff->y)) ); return $diff_sec; } } } } public function addTwoTimes($time1 = "00:00", $time2 = "00:00") { $times = [$time1, $time2]; $hours = 0; $minutes = 0; $seconds = 0; foreach ($times as $time) { if ($this->Contains($time, ":")) { list($hour, $minute) = explode(':', $time); $seconds += $hour * 3600; $seconds += $minute * 60; } } $hours = floor($seconds / 3600); $seconds -= $hours * 3600; $minutes = floor($seconds / 60); $seconds -= $minutes * 60; if ($seconds < 9) { $seconds = "0" . $seconds; } if ($minutes < 9) { $minutes = "0" . $minutes; } if ($hours < 9) { $hours = "0" . $hours; } return "{$hours}:{$minutes}"; } public function Contains($haystack, $needle) { if (strpos($haystack, $needle) !== false) { return true; } else { return false; } } public function getShiftTotalFromRange($employee, $date_range_start, $date_range_end) { $shift_total_time = "00:00"; $shift_array = []; $wage_total = floatval(0); $count = 0; $loop = new WP_Query(['post_type' => 'shift', 'author' => $employee, 'posts_per_page' => -1]); while ($loop->have_posts()): $loop->the_post(); $shift_sum = "00:00"; $shift_id = $loop->post->ID; $custom = get_post_custom($shift_id); $employee_clock_in_time = isset($custom["employee_clock_in_time"][0]) ? sanitize_text_field($custom["employee_clock_in_time"][0]) : null; $employee_clock_out_time = isset($custom["employee_clock_out_time"][0]) ? sanitize_text_field($custom["employee_clock_out_time"][0]) : null; $searchDateBegin = sanitize_text_field($date_range_start); $searchDateEnd = sanitize_text_field($date_range_end); if ((strtotime($employee_clock_in_time) >= strtotime($searchDateBegin)) && (strtotime($employee_clock_in_time) <= strtotime($searchDateEnd))) { $author_id = $loop->post->post_author; $last_name = sanitize_text_field(get_the_author_meta('last_name', $author_id)); $first_name = sanitize_text_field(get_the_author_meta('first_name', $author_id)); $wage = sanitize_text_field(get_the_author_meta('employee_wage', $author_id)); if ($employee_clock_in_time != null && $employee_clock_out_time != null) { $shift_sum = $this->secondsToTime($this->dateDifference($employee_clock_in_time, $employee_clock_out_time)); if (! $this->isNull($wage)) { $decimal_total = $this->TimeToDecimal($shift_sum); $wage_total += (floatval($wage) * floatval($decimal_total)); } $shift_total_time = $this->addTwoTimes($shift_total_time, $shift_sum); } array_push( $shift_array, [ "shift_id" => $shift_id, "employee_clock_in_time" => $this->cleanDate($employee_clock_in_time), "employee_clock_out_time" => $this->cleanDate($employee_clock_out_time), "first_name" => $first_name, "last_name" => $last_name, "shift_sum" => $shift_sum, ] ); $count++; } endwhile; wp_reset_query(); return [ "response" => "success", "shift_count" => $count, "shift_total_time" => $shift_total_time, "wage_total" => $this->Money($wage_total), "shift_array" => $shift_array, ]; } public function TimeToDecimal($time) { $timeArr = explode(':', $time); $decTime = ($timeArr[0] * 60) + ($timeArr[1]); return ($decTime / 60); } public function Money($amount) { return number_format($amount, 2); } public function isNull($string) { if ($string == null || $string == '') { return true; } else { return false; } } public function modifyWageLite($profile_fields) { $profile_fields['employee_wage'] = esc_attr_x('Wage', 'aio-time-clock-lite', 'aio-time-clock-lite'); return $profile_fields; } public function adminNoticesLite() { /* if (get_option('aio_time_clock_lite_update_redirect', false)) { ?>

mysqlDateFormat); return [ "response" => "success", "timezone_option" => $timezone_string, "current_time" => $current_time, "time_type" => "wordpress", "timezone" => $timezone_string, ]; } public function getDateTimeFormat() { $date_format = sanitize_text_field(get_option('date_format')); $time_format = sanitize_text_field(get_option('time_format')); if ($date_format == null) { $date_format = 'Y-m-d'; } if ($time_format == null) { $time_format = 'h:i:s A'; } return $date_format . " " . $time_format; } public function getDateFormat() { $date_format = get_option('date_format'); if ($date_format == null) { $date_format = 'Y-m-d'; } return $date_format; } public function getTimeFormat() { $time_format = get_option('time_format'); if ($time_format == null) { $time_format = 'h:i:s A'; } return $time_format; } public function isTimeMachine($date) { $now = date("Y-m-d h:i:s A"); $end_date = date("Y-m-d", strtotime(date("Y-m-d", strtotime($now)) . " - 10 years")); return (strtotime($date) <= strtotime($end_date)); } public function aio_lite_update_department_count($terms, $taxonomy) { global $wpdb; foreach ((array) $terms as $term) { $count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", intval($term))); do_action('edit_term_taxonomy', $term, $taxonomy); $wpdb->update($wpdb->term_taxonomy, compact('count'), ['term_taxonomy_id' => $term]); do_action('edited_term_taxonomy', $term, $taxonomy); } } public function aio_lite_register_user_taxonomy() { register_taxonomy( 'department', 'user', [ 'public' => true, 'labels' => [ 'name' => esc_attr_x('Department', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'singular_name' => esc_attr_x('Department', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'menu_name' => esc_attr_x('Departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'search_items' => esc_attr_x('Search Departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'popular_items' => esc_attr_x('Popular Departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'all_items' => esc_attr_x('All Departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'edit_item' => esc_attr_x('Edit Department', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'update_item' => esc_attr_x('Update Department', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'add_new_item' => esc_attr_x('Add New Department', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'new_item_name' => esc_attr_x('New Department Name', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'separate_items_with_commas' => esc_attr_x('Separate departments with commas', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'add_or_remove_items' => esc_attr_x('Add or remove departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'choose_from_most_used' => esc_attr_x('Choose from the most popular departments', 'aio-time-clock-lite', 'aio-time-clock-lite'), ], 'rewrite' => [ 'with_front' => true, 'slug' => 'author/department', // Use 'author' (default WP user slug). ], 'capabilities' => [ 'manage_terms' => 'edit_users', // Using 'edit_users' cap to keep this simple. 'edit_terms' => 'edit_users', 'delete_terms' => 'edit_users', 'assign_terms' => 'read', ], 'update_count_callback' => 'aio_lite_update_department_count', // Use a custom function to update the count. ] ); } public function aio_tc_lite_save_user_department_terms($user_id) { $tax = get_taxonomy('department'); if (! current_user_can('edit_user', $user_id) && current_user_can($tax->cap->assign_terms)) { return false; } if (isset($_POST['department'])) { $term = sanitize_text_field($_POST['department']); wp_set_object_terms($user_id, [$term], 'department', false); } clean_object_term_cache($user_id, 'department'); } public function aio_lite_add_department_admin_page() { $tax = get_taxonomy('department'); add_users_page( esc_attr($tax->labels->menu_name), esc_attr($tax->labels->menu_name), $tax->cap->manage_terms, 'edit-tags.php?taxonomy=' . $tax->name ); } public function aio_lite_manage_department_user_column($columns) { unset($columns['posts']); $columns['users'] = esc_attr_x('Users', 'aio-time-clock-lite', 'aio-time-clock-lite'); return $columns; } public function aio_lite_manage_department_column($display, $column, $term_id) { if ('users' === $column) { $term = get_term($term_id, 'department'); echo esc_attr($term->count); } } public function aio_lite_save_user_department_terms($user_id) { $tax = get_taxonomy('department'); if (! current_user_can('edit_user', $user_id) && current_user_can($tax->cap->assign_terms)) { return false; } if (isset($_POST['department'])) { $term = sanitize_text_field($_POST['department']); wp_set_object_terms(intval($user_id), [$term], 'department', false); } clean_object_term_cache(intval($user_id), 'department'); } public function aio_lite_disable_username($username) { if ('department' === $username) { $username = ''; } return esc_attr($username); } public function isValidDate($date) { if ($date != null) { $date = str_replace('/', '-', $date); return (bool) strtotime($date); } else { return false; } } public function loadWidgets() { register_widget('AIO_Time_Clock_Lite_Widgets'); } public function checkPermission($user_roles) { $found = 0; global $current_user; $original_roles = $this->getBuiltInRoles(); if (isset($original_roles)) { foreach ($user_roles as $r) { if (in_array($r, $original_roles, false)) { $found++; } } } if (current_user_can('administrator')) { $found++; } if ($found > 0) { return true; } else { return false; } } public function cleanDate($date) { if ($this->isValidDate($date) && $date != null) { return date($this->getDateTimeFormat(), strtotime($date)); } else { return null; } } public function getUsers() { $users = []; $blog_id = get_current_blog_id(); $built_in_roles = $this->getBuiltInRoles(); $args = [ 'blog_id' => $blog_id, 'role' => '', 'role__in' => $built_in_roles, 'role__not_in' => [], 'meta_key' => 'last_name', 'meta_value' => '', 'meta_compare' => '', 'meta_query' => [], 'date_query' => [], 'include' => [], 'exclude' => [], 'orderby' => 'meta_value', 'order' => 'ASC', 'offset' => '', 'search' => '', 'number' => '', 'count_total' => false, 'fields' => 'all', 'who' => '', ]; $your_users = get_users($args); foreach ($your_users as $user) { array_push( $users, [ "employee_id" => $user->ID, "first_name" => $user->first_name, "last_name" => $user->last_name, "department" => $this->getDepartment($user->ID), "roles" => $user->roles, ] ); } return $users; } public function getEmployeeName($employee_id) { $full_name = null; $employee_id = intval($employee_id); $first_name = sanitize_text_field(get_the_author_meta('first_name', $employee_id)); $last_name = sanitize_text_field(get_the_author_meta('last_name', $employee_id)); if (($first_name == null || $last_name == null)) { if ($employee_id != null) { $user_info = get_userdata($employee_id); if (isset($user_info)) { $full_name = sanitize_text_field($user_info->last_name) . ", " . sanitize_text_field($user_info->first_name); } } } else { $full_name = $last_name . ", " . $first_name; } return $full_name; } public function getDepartment($user_id) { $department = ""; $user_groups = wp_get_object_terms($user_id, 'department', ['fields' => 'all_with_object_id']); // Get user group detail foreach ($user_groups as $user_gro) { $department = $user_gro->name; // Get current user group name } return $department; } public function aio_lite_edit_user_department_section($user) { require_once "templates/department-section.php"; } public function getMonitoringShiftColumn($employee_id) { return '
' . esc_attr_x('Working', 'aio-time-clock', 'aio-time-clock-lite') . '
'; } public function getTranslationStrings($context = 'front') { $nonce_action = ($context === 'admin') ? 'aio_tcl_admin' : 'aio_tcl_front'; return [ 'Nonce' => wp_create_nonce($nonce_action), 'ajaxurl' => admin_url('admin-ajax.php'), 'isClockedIn' => esc_attr_x('You are currently clocked in', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockInTime' => esc_attr_x('Clock In Time', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'updateNote' => esc_attr_x('Update Note', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'addNote' => esc_attr_x('Add Note', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockInMessage' => esc_attr_x('Click CLOCK IN to START your shift', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'locationError' => esc_attr_x('Location Required', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockedOutMessage' => esc_attr_x('You have been clocked out', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockOutFail' => esc_attr_x('Clock out failed', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockInFail' => esc_attr_x('Clock In failed', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'currentTime' => esc_attr_x('Current Time', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockIn' => esc_attr_x('Clock In', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'clockOut' => esc_attr_x('Clock Out', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'Name' => esc_attr_x('Name', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'Options' => esc_attr_x('Options', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'ShiftTotal' => esc_attr_x('Shift Total', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'TotalShifts' => esc_attr_x('Total Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'TotalShiftTime' => esc_attr_x('Total Shift Time', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'WageTotal' => esc_attr_x('Wage Total', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'TimeClockDetected' => esc_attr_x('Widget time clock disabled while on time clock page', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'PageLinkEmpty' => esc_attr_x('Page link not available so cannot redirect', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'ViewPage' => esc_attr_x('View Page', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'EditPage' => esc_attr_x('Edit Page', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'Close' => esc_attr_x('Close', 'aio-time-clock-lite', 'aio-time-clock-lite'), 'Cancel' => esc_attr_x('Cancel', 'aio-time-clock-lite', 'aio-time-clock-lite'), ]; } public function getProFeatures() { $features = [ [ "title" => esc_attr_x('More Settings', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Easily customize the time clock for your company', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/1-settings.png", ], [ "title" => esc_attr_x('Custom Roles', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Add your own custom roles for time clock access', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/2-custom-roles.png", ], [ "title" => esc_attr_x('Manager Profiles', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Manager profiles that can export, import and edit shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/3-manager-profile.png", ], [ "title" => esc_attr_x('Improved Reports', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Custom Simple and Advanced Shift Reports', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/4-reports.png", ], [ "title" => esc_attr_x('Charts and Graphs', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('View activity in the form of a chart', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/5-charts.png", ], [ "title" => esc_attr_x('Multiple Shift Views', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('View, edit or create bulk shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/6-shift-views.png", ], [ "title" => esc_attr_x('Predefined Locations', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Unlimited Clock in Locations', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/7-locations.png", ], [ "title" => esc_attr_x('IP And GPS Tracking', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Shifts can show the location of an IP address and a GPS coordinate', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/8-shift-info.png", ], [ "title" => esc_attr_x('Quick Pick Time Clock', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('List style time clock page with pin pad entry instead of a login', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/9-quick-pick.png", ], [ "title" => esc_attr_x('Predefined Shifts', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Create prefined shifts that employees can choose from instead of a dynamic clock', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/10-predefined-shifts.png", ], [ "title" => esc_attr_x('Extendable', 'aio-time-clock-lite', 'aio-time-clock-lite'), "description" => esc_attr_x('Extensions/Addons Supported to allow anything to be possible', 'aio-time-clock-lite', 'aio-time-clock-lite'), "image" => "/images/pro/11-extendable.png", ], ]; return $features; } private function require_auth_front(): void { if (! is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required'], 401); } // For employees clocking themselves in/out. if (! current_user_can('read')) { wp_send_json_error(['message' => 'Forbidden'], 403); } } private function require_auth_manage(): void { if (! is_user_logged_in()) { wp_send_json_error(['message' => 'Authentication required'], 401); } // Manager/admin-only for page creation & reports. if (! current_user_can('edit_posts')) { // Or use a custom cap like 'aio_tc_manage' if you prefer wp_send_json_error(['message' => 'Forbidden'], 403); } } private function check_ajax_nonce(string $action, string $field = 'nonce'): void { if (false === check_ajax_referer($action, $field, false)) { wp_send_json_error(['message' => 'Bad nonce'], 403); } } }