count_pending_workflows(); $badge = ''; if ($pending_count > 0) { $badge = sprintf( ' %2$s', (int) $pending_count, esc_html(sprintf( /* translators: %d: number of orders awaiting approval */ _n('%d order awaiting approval', '%d orders awaiting approval', $pending_count, 'crewcommerce'), $pending_count )) ); } add_menu_page(__('CrewCommerce', 'crewcommerce'), __('CrewCommerce', 'crewcommerce') . $badge, 'manage_options', 'crewcommerce', array($this, 'dashboard_page'), 'dashicons-groups', 56); add_submenu_page('crewcommerce', __('Dashboard', 'crewcommerce'), __('Dashboard', 'crewcommerce'), 'manage_options', 'crewcommerce', array($this, 'dashboard_page')); add_submenu_page('crewcommerce', __('Companies', 'crewcommerce'), __('Companies', 'crewcommerce'), 'manage_options', 'crewcommerce-companies', array($this, 'companies_page')); add_submenu_page('crewcommerce', __('Approvals', 'crewcommerce'), __('Approvals', 'crewcommerce') . $badge, 'manage_options', 'crewcommerce-approvals', array($this, 'approvals_page')); add_submenu_page('crewcommerce', __('Settings', 'crewcommerce'), __('Settings', 'crewcommerce'), 'manage_options', 'crewcommerce-settings', array($this, 'settings_page')); if (!self::pro_active()) { add_submenu_page('crewcommerce', __('CrewCommerce Pro', 'crewcommerce'), __('Upgrade to Pro', 'crewcommerce'), 'manage_options', 'crewcommerce-pro', array($this, 'pro_page'), 99); } } /* ------------------------------------------------------------------ * Settings * ---------------------------------------------------------------- */ public function register_settings() { register_setting('crewcommerce_settings', 'crewcommerce_settings', array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_settings'), )); add_settings_section('crewcommerce_approval_settings', __('Approval Rules', 'crewcommerce'), array($this, 'approval_settings_section_callback'), 'crewcommerce_settings'); add_settings_field('approval_threshold', __('Order Value Threshold', 'crewcommerce'), array($this, 'approval_threshold_callback'), 'crewcommerce_settings', 'crewcommerce_approval_settings'); add_settings_field('require_approval_roles', __('Apply Rules To', 'crewcommerce'), array($this, 'approval_roles_callback'), 'crewcommerce_settings', 'crewcommerce_approval_settings'); add_settings_section('crewcommerce_workflow_settings', __('Workflow', 'crewcommerce'), array($this, 'workflow_settings_section_callback'), 'crewcommerce_settings'); add_settings_field('approval_required_message', __('Order Confirmation Message', 'crewcommerce'), array($this, 'approval_message_callback'), 'crewcommerce_settings', 'crewcommerce_workflow_settings'); add_settings_section('crewcommerce_data_settings', __('Data', 'crewcommerce'), '__return_false', 'crewcommerce_settings'); add_settings_field('delete_data_on_uninstall', __('When CrewCommerce Is Deleted', 'crewcommerce'), array($this, 'delete_data_on_uninstall_callback'), 'crewcommerce_settings', 'crewcommerce_data_settings'); } /** * Sanitize the settings this plugin owns. Keys it doesn't know (an * add-on's settings) are kept as stored and passed to the * crewcommerce_sanitize_settings filter. */ public function sanitize_settings($input) { $input = is_array($input) ? $input : array(); $sanitized = get_option('crewcommerce_settings', array()); $sanitized = is_array($sanitized) ? $sanitized : array(); $sanitized['approval_threshold'] = isset($input['approval_threshold']) ? max(0, (float) $input['approval_threshold']) : 0; $roles = isset($input['require_approval_roles']) ? array_map('sanitize_key', (array) $input['require_approval_roles']) : array(); $sanitized['require_approval_roles'] = array_values(array_intersect($roles, array('requester', 'manager'))); $sanitized['approval_required_message'] = isset($input['approval_required_message']) ? sanitize_textarea_field($input['approval_required_message']) : ''; $sanitized['delete_data_on_uninstall'] = !empty($input['delete_data_on_uninstall']); /** * Sanitize settings added by an add-on. * * @param array $sanitized The settings to save. * @param array $input The raw submitted values. */ return apply_filters('crewcommerce_sanitize_settings', $sanitized, $input); } public function approval_settings_section_callback() { echo '

' . esc_html__('Decide which orders wait for a manager at the customer’s company to approve them.', 'crewcommerce') . '

'; } public function workflow_settings_section_callback() { echo '

' . esc_html__('What people see once an order is held for approval.', 'crewcommerce') . '

'; } public function approval_threshold_callback() { $settings = get_option('crewcommerce_settings', array()); $value = isset($settings['approval_threshold']) ? (float) $settings['approval_threshold'] : 0; $symbol = html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES); ?>

get_roles_checked(); $roles = array( 'requester' => __('Requesters — team members who place orders', 'crewcommerce'), 'manager' => __('Managers — their orders go to another Manager or the Admin', 'crewcommerce'), ); ?>
$label) : ?>

get_order_statistics(); $counts = wp_count_posts('crewcommerce_company'); $company_cnt = isset($counts->publish) ? (int) $counts->publish : 0; $approvals = CrewCommerce_Approvals::get_instance(); $stale_hours = $approvals->get_stale_hours(); $items = array(); $unassigned = $approvals->get_unassigned_pending_workflows(); if (!empty($unassigned)) { $items[] = array( /* translators: %d: number of orders */ 'text' => sprintf(_n('%d order is waiting for someone to claim it — its company has no Manager or Admin.', '%d orders are waiting for someone to claim them — their companies have no Manager or Admin.', count($unassigned), 'crewcommerce'), count($unassigned)), 'link' => admin_url('admin.php?page=crewcommerce-approvals'), 'cta' => __('Claim an order', 'crewcommerce'), ); } $missing = CrewCommerce_Team_Accounts::get_instance()->get_companies_missing_approvers(); foreach ($missing as $company) { $items[] = array( /* translators: %s: company name */ 'text' => sprintf(__('%s has no Manager or Admin, so its orders that need approval will wait for you to claim them.', 'crewcommerce'), $company->post_title), 'link' => admin_url('admin.php?page=crewcommerce-companies&company=' . $company->ID), 'cta' => __('Open the company', 'crewcommerce'), ); } $stale = $approvals->get_stale_pending_workflows($stale_hours); if (!empty($stale)) { $items[] = array( /* translators: 1: number of orders, 2: number of hours */ 'text' => sprintf(_n('%1$d order has waited more than %2$d hours for approval.', '%1$d orders have waited more than %2$d hours for approval.', count($stale), 'crewcommerce'), count($stale), $stale_hours), 'link' => admin_url('admin.php?page=crewcommerce-approvals'), 'cta' => __('Review approvals', 'crewcommerce'), ); } /** * Items for the dashboard's "Needs your attention" box. Each item is * an array with text, link and cta (link text). * * @param array $items Attention items. */ $items = (array) apply_filters('crewcommerce_admin_attention_items', $items); ?>

  1. —
  2. —
  3. —

render_recent_activity(); ?>

get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery "SELECT aw.*, u1.display_name AS requester_name, u2.display_name AS approver_name FROM {$wpdb->prefix}crewcommerce_approval_workflows aw LEFT JOIN {$wpdb->users} u1 ON aw.requester_id = u1.ID LEFT JOIN {$wpdb->users} u2 ON aw.approver_id = u2.ID ORDER BY aw.updated_at DESC LIMIT 10" ); if (empty($rows)) { echo '

' . esc_html__('No approvals yet.', 'crewcommerce') . '

'; return; } $now = current_time('timestamp'); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested -- compared with updated_at, stored in site time. echo ''; } /* ------------------------------------------------------------------ * Companies * ---------------------------------------------------------------- */ public function companies_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only view selection. $company_id = isset($_GET['company']) ? absint(wp_unslash($_GET['company'])) : 0; if ($company_id) { $this->company_detail_page($company_id); return; } $companies = get_posts(array( 'post_type' => 'crewcommerce_company', 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC', )); $team_accounts = CrewCommerce_Team_Accounts::get_instance(); ?>


ID, '_company_admin', true); $admin = $admin_id ? get_user_by('ID', $admin_id) : null; $detail = admin_url('admin.php?page=crewcommerce-companies&company=' . $company->ID); ?>
post_title); ?>
|
display_name) : '—'; ?> get_team_members($company->ID))); ?> post_date))); ?>
post_type) { echo '

' . esc_html__('Company not found.', 'crewcommerce') . '

'; return; } $team_accounts = CrewCommerce_Team_Accounts::get_instance(); $team_members = $team_accounts->get_team_members($company_id); $admin_id = get_post_meta($company_id, '_company_admin', true); /** * Extra columns for a company's team table in WP Admin, as key => * heading. Each cell is rendered by the * crewcommerce_admin_team_column_{key} action. * * @param array $columns Extra columns. * @param int $company_id The company. */ $extra_columns = (array) apply_filters('crewcommerce_admin_team_columns', array(), (int) $company_id); ?>

←

post_title); ?>


display_name); ?> user_id) : ?>
user_email); ?> created_at))); ?>
get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery "SELECT aw.*, u1.display_name AS requester_name, u2.display_name AS approver_name FROM {$wpdb->prefix}crewcommerce_approval_workflows aw LEFT JOIN {$wpdb->users} u1 ON aw.requester_id = u1.ID LEFT JOIN {$wpdb->users} u2 ON aw.approver_id = u2.ID ORDER BY aw.created_at DESC LIMIT 50" ); $stale_hours = CrewCommerce_Approvals::get_instance()->get_stale_hours(); $now = current_time('timestamp'); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested -- compared with created_at, stored in site time. $labels = self::workflow_status_labels(); ?>


order_id); $is_pending = ('pending' === $workflow->status); $is_stale = $is_pending && ($now - strtotime($workflow->created_at)) >= ($stale_hours * HOUR_IN_SECONDS); ?>
#get_order_number()); ?> #order_id); ?> requester_name ? $workflow->requester_name : '—'); ?> approver_name ? $workflow->approver_name : '—'); ?> status]) ? $labels[$workflow->status] : ucfirst($workflow->status)); ?> created_at), $now)); ?> — updated_at))); ?> status) : ?> —
__('Pending', 'crewcommerce'), 'unassigned' => __('Needs an approver', 'crewcommerce'), 'approved' => __('Approved', 'crewcommerce'), 'rejected' => __('Rejected', 'crewcommerce'), 'cancelled' => __('Cancelled', 'crewcommerce'), )); } /* ------------------------------------------------------------------ * Pro * ---------------------------------------------------------------- */ public function pro_page() { $features = array( array( __('Spending limits', 'crewcommerce'), __('Give each buyer a monthly budget. Checkout stops an order that would go over it, buyers see how much they have left, and Spend Reports show everyone close to their limit.', 'crewcommerce'), ), array( __('Rules for each customer', 'crewcommerce'), __('A different approval threshold for each company, product categories that always need approval, and two approval steps (a Manager, then the Admin).', 'crewcommerce'), ), array( __('Faster decisions', 'crewcommerce'), __('Questions and answers on each order before it’s decided, approving or rejecting several orders at once, and reminder emails when an approval sits too long.', 'crewcommerce'), ), array( __('Customers set themselves up', 'crewcommerce'), __('Customers create their company when they register, and colleagues join with a private link. You only approve the requests.', 'crewcommerce'), ), array( __('Fast Restock', 'crewcommerce'), __('A list of everything a company orders regularly, so a buyer can tick, set quantities and add it all to the cart at once.', 'crewcommerce'), ), array( __('Records', 'crewcommerce'), __('Company Order History for each customer’s managers, an Activity Log of team changes, and a CSV export of every approval.', 'crewcommerce'), ), ); ?>