admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wpim_lend_ajax' ),
'i18n' => [
'remove' => __( 'Remove', 'borrowdesk' ),
'available' => __( 'available', 'borrowdesk' ),
'chooseUnit' => __( 'Choose a unit', 'borrowdesk' ),
'noResults' => __( 'No matches', 'borrowdesk' ),
'notLendable' => __( 'not set up for lending', 'borrowdesk' ),
],
] );
}
/* ================================================================== helpers */
private static function guard( $nonce_action ) {
if ( ! WPIMLend::can_manage() ) {
wp_die( esc_html__( 'You are not allowed to do that.', 'borrowdesk' ), 403 );
}
check_admin_referer( $nonce_action );
}
private static function redirect( $page, array $args = [] ) {
wp_safe_redirect( add_query_arg( array_merge( [ 'page' => $page ], $args ), admin_url( 'admin.php' ) ) );
exit;
}
private static function flash( $message, $type = 'success' ) {
set_transient( 'wpim_lend_flash_' . get_current_user_id(), [ 'message' => $message, 'type' => $type ], 60 );
}
private static function render_flash() {
$key = 'wpim_lend_flash_' . get_current_user_id();
$flash = get_transient( $key );
if ( $flash ) {
delete_transient( $key );
printf( '
', esc_attr( 'error' === $flash['type'] ? 'error' : 'success' ), esc_html( $flash['message'] ) );
}
}
private static function post( $key, $default = '' ) {
return isset( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : $default; // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- nonce checked in guard(); each caller sanitizes
}
private static function get( $key, $default = '' ) {
return isset( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : $default; // phpcs:ignore WordPress.Security.NonceVerification
}
public static function format_date( $ymd ) {
return $ymd ? date_i18n( get_option( 'date_format' ), strtotime( $ymd . ' 12:00:00' ) ) : '';
}
public static function format_utc( $datetime ) {
return $datetime ? get_date_from_gmt( $datetime, get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) : '';
}
private static function item_label( $core ) {
if ( ! $core ) {
return __( '(deleted item)', 'borrowdesk' );
}
return $core->inventory_number ? $core->inventory_number . ' — ' . $core->inventory_name : $core->inventory_name;
}
/* ================================================================== dashboard */
public static function page_dashboard() {
if ( ! WPIMLend::can_manage() ) {
return;
}
$loan_id = absint( self::get( 'loan' ) );
if ( $loan_id ) {
self::page_loan( $loan_id );
return;
}
$views = [
'open' => __( 'On loan', 'borrowdesk' ),
'due_today' => __( 'Due today', 'borrowdesk' ),
'overdue' => __( 'Overdue', 'borrowdesk' ),
'returned' => __( 'Returned (30 days)', 'borrowdesk' ),
];
$view = self::get( 'view', 'open' );
$view = isset( $views[ $view ] ) ? $view : 'open';
$today = WPIMLend_Availability::today();
$filters = [
'borrower_id' => absint( self::get( 'borrower_id' ) ),
'inventory_id' => absint( self::get( 'inventory_id' ) ),
'group' => self::get( 'group' ),
];
$paged = max( 1, absint( self::get( 'paged', 1 ) ) );
$total = WPIMLend_Repo::list_loans( $view, $today, $filters, 0, 0, TRUE );
$loans = WPIMLend_Repo::list_loans( $view, $today, $filters, self::PER_PAGE, ( $paged - 1 ) * self::PER_PAGE );
$lines = WPIMLend_Repo::lines_for_loans( wp_list_pluck( $loans, 'loan_id' ) );
$item_ids = [];
foreach ( $lines as $ls ) {
foreach ( $ls as $l ) {
$item_ids[] = (int) $l->inventory_id;
}
}
$core = WPIMLend_Repo::get_core_items( $item_ids );
$base = admin_url( 'admin.php?page=' . self::PAGE_DASHBOARD );
?>
$label ) {
$count = WPIMLend_Repo::list_loans( $key, $today, [], 0, 0, TRUE );
$links[] = sprintf(
'- %s (%d)',
esc_url( add_query_arg( 'view', $key, $base ) ),
$key === $view ? 'current' : '',
esc_html( $label ),
(int) $count
);
}
echo implode( ' |
', $links ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above
?>
|
|
|
|
|
|
status && $loan->due_date < $today;
$view_url = add_query_arg( 'loan', $loan->loan_id, $base );
?>
borrower_name ); ?>
borrower_group ) : ?> borrower_group ); ?>
|
loan_id ] ) ? $lines[ $loan->loan_id ] : [] as $line ) :
$out = (int) $line->quantity - (int) $line->returned_quantity;
$unit = $line->unit_id ? WPIMLend_Repo::get_unit( $line->unit_id ) : NULL;
?>
inventory_id ] ) ? $core[ $line->inventory_id ] : NULL ) ); ?>
code ); ?>
× quantity; ?>
status && $out < (int) $line->quantity && $out > 0 ) : ?>
()
|
out_at ) ); ?> |
due_date ) ); ?>
|
status ? esc_html__( 'Return / extend', 'borrowdesk' ) : esc_html__( 'View', 'borrowdesk' ); ?>
status && $loan->borrower_email ) : ?>
|
';
echo wp_kses_post( paginate_links( [
'base' => add_query_arg( 'paged', '%#%', $url ),
'format' => '',
'current' => $paged,
'total' => $pages,
] ) );
echo '
';
}
/* ================================================================== single loan: return / extend */
private static function page_loan( $loan_id ) {
$loan = WPIMLend_Repo::get_loan( $loan_id );
if ( ! $loan ) {
echo '' . esc_html__( 'Loan not found.', 'borrowdesk' ) . '
';
return;
}
$borrower = WPIMLend_Repo::get_borrower( $loan->borrower_id );
$lines = WPIMLend_Repo::get_lines( $loan_id );
$core = WPIMLend_Repo::get_core_items( wp_list_pluck( $lines, 'inventory_id' ) );
$returns = WPIMLend_Repo::get_return_events( wp_list_pluck( $lines, 'line_id' ) );
$open = 'open' === $loan->status;
?>
loan_id ); ?>
←
- returned_at ), $r->quantity, $r->item_condition, $r->note ? ': ' . $r->note : '' ) ); ?>
$row ) {
$qty = isset( $row['quantity'] ) ? (int) $row['quantity'] : 0;
if ( $qty < 1 ) {
continue;
}
$line = WPIMLend_Repo::get_line( absint( $line_id ) );
if ( ! $line || (int) $line->loan_id !== $loan_id ) {
continue;
}
$any = TRUE;
$r = WPIMLend_Service::return_line( $line->line_id, $qty, sanitize_key( $row['condition'] ?? 'ok' ), sanitize_text_field( $row['note'] ?? '' ) );
if ( is_wp_error( $r ) ) {
$result = $r;
}
}
if ( ! $any ) {
$result = new WP_Error( 'none', __( 'Enter a quantity to return, or use "Return everything".', 'borrowdesk' ) );
}
}
if ( is_wp_error( $result ) ) {
self::flash( $result->get_error_message(), 'error' );
} else {
self::flash( __( 'Return recorded.', 'borrowdesk' ) );
}
self::redirect( self::PAGE_DASHBOARD, [ 'loan' => $loan_id ] );
}
public static function handle_extend() {
$loan_id = absint( self::post( 'loan_id' ) );
self::guard( 'wpim_lend_extend_' . $loan_id );
$result = WPIMLend_Service::extend( $loan_id, sanitize_text_field( self::post( 'due_date' ) ) );
self::flash( is_wp_error( $result ) ? $result->get_error_message() : __( 'Due date changed.', 'borrowdesk' ), is_wp_error( $result ) ? 'error' : 'success' );
self::redirect( self::PAGE_DASHBOARD, [ 'loan' => $loan_id ] );
}
public static function handle_remind() {
$loan_id = absint( self::get( 'loan' ) );
self::guard( 'wpim_lend_remind_' . $loan_id );
$sent = WPIMLend_Mail::send_reminder_for_loan( $loan_id );
self::flash( $sent ? __( 'Reminder sent.', 'borrowdesk' ) : __( 'The reminder could not be sent (no email address?).', 'borrowdesk' ), $sent ? 'success' : 'error' );
self::redirect( self::PAGE_DASHBOARD );
}
/* ================================================================== check out */
public static function page_checkout() {
if ( ! WPIMLend::can_manage() ) {
return;
}
$preset_borrower = absint( self::get( 'borrower' ) );
$borrower = $preset_borrower ? WPIMLend_Repo::get_borrower( $preset_borrower ) : NULL;
?>
absint( $row['inventory_id'] ?? 0 ),
'quantity' => (int) ( $row['quantity'] ?? 1 ),
'unit_id' => absint( $row['unit_id'] ?? 0 ),
];
}
$result = WPIMLend_Service::checkout( $borrower_id, $lines, sanitize_text_field( self::post( 'due_date' ) ), sanitize_textarea_field( self::post( 'notes' ) ) );
if ( is_wp_error( $result ) ) {
self::flash( $result->get_error_message(), 'error' );
self::redirect( self::PAGE_CHECKOUT, $borrower_id ? [ 'borrower' => $borrower_id ] : [] );
}
self::flash( __( 'Checked out.', 'borrowdesk' ) );
self::redirect( self::PAGE_DASHBOARD, [ 'loan' => $result ] );
}
/* ================================================================== AJAX */
private static function ajax_guard() {
if ( ! WPIMLend::can_manage() || ! check_ajax_referer( 'wpim_lend_ajax', 'nonce', FALSE ) ) {
wp_send_json_error( [], 403 );
}
}
public static function ajax_search_borrowers() {
self::ajax_guard();
$term = sanitize_text_field( wp_unslash( $_GET['term'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
$out = [];
foreach ( WPIMLend_Repo::search_borrowers( $term, 15 ) as $b ) {
$out[] = [
'id' => (int) $b->borrower_id,
'label' => $b->name . ( $b->email ? ' <' . $b->email . '>' : '' ) . ( $b->borrower_group ? ' — ' . $b->borrower_group : '' ),
'name' => $b->name,
];
}
wp_send_json_success( $out );
}
public static function ajax_search_items() {
self::ajax_guard();
$term = sanitize_text_field( wp_unslash( $_GET['term'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
$items = WPIMLend_Repo::search_core_items( $term, 20 );
$avail = WPIMLend_Availability::for_items( wp_list_pluck( $items, 'inventory_id' ) );
$out = [];
foreach ( $items as $item ) {
$a = isset( $avail[ $item->inventory_id ] ) ? $avail[ $item->inventory_id ] : NULL;
if ( ! $a ) {
continue;
}
$out[] = [
'id' => (int) $item->inventory_id,
'label' => self::item_label( $item ),
'available' => max( 0, (int) $a['available'] ),
'owned' => (int) $a['owned'],
'lendable' => (bool) $a['lendable'],
'trackUnits' => (bool) $a['track_units'],
'dueDate' => WPIMLend_Availability::default_due_date( $item->inventory_id ),
];
}
wp_send_json_success( $out );
}
public static function ajax_item_units() {
self::ajax_guard();
$inventory_id = absint( $_GET['inventory_id'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification
$on_loan = WPIMLend_Repo::units_on_loan( $inventory_id );
$out = [];
foreach ( WPIMLend_Repo::get_units( $inventory_id ) as $unit ) {
if ( 'available' === $unit->status && ! isset( $on_loan[ (int) $unit->unit_id ] ) ) {
$out[] = [
'id' => (int) $unit->unit_id,
'label' => $unit->code . ( $unit->serial ? ' (' . $unit->serial . ')' : '' ),
];
}
}
wp_send_json_success( $out );
}
/* ================================================================== borrowers */
private static function clean_borrower( array $in ) {
return [
'name' => sanitize_text_field( $in['name'] ?? '' ),
'email' => sanitize_email( $in['email'] ?? '' ),
'phone' => sanitize_text_field( $in['phone'] ?? '' ),
'borrower_group' => sanitize_text_field( $in['borrower_group'] ?? '' ),
'notes' => sanitize_textarea_field( $in['notes'] ?? '' ),
'user_id' => absint( $in['user_id'] ?? 0 ),
];
}
public static function page_borrowers() {
if ( ! WPIMLend::can_manage() ) {
return;
}
$borrower_id = absint( self::get( 'borrower' ) );
$editing = 'new' === self::get( 'borrower' ) || $borrower_id;
?>
|
|
|
|
|
|
| name ); ?> |
email ); ?> |
phone ); ?> |
borrower_group ); ?> |
|
$b->borrower_id ], 100 );
$lines = WPIMLend_Repo::lines_for_loans( wp_list_pluck( $loans, 'loan_id' ) );
$ids = [];
foreach ( $lines as $ls ) {
foreach ( $ls as $l ) {
$ids[] = $l->inventory_id;
}
}
$core = WPIMLend_Repo::get_core_items( $ids );
?>
'new' ] );
}
$id = WPIMLend_Repo::save_borrower( $data, absint( self::post( 'borrower_id' ) ) );
self::flash( $id ? __( 'Borrower saved.', 'borrowdesk' ) : __( 'The borrower could not be saved.', 'borrowdesk' ), $id ? 'success' : 'error' );
self::redirect( self::PAGE_BORROWERS, $id ? [ 'borrower' => $id ] : [] );
}
public static function handle_borrower_import() {
self::guard( 'wpim_lend_borrower_import' );
$result = WPIMLend_CSV::import_borrowers( $_FILES['csv'] ?? NULL ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing -- nonce checked in guard(); the file is validated in import_borrowers()
self::flash( $result['message'], $result['ok'] ? 'success' : 'error' );
self::redirect( self::PAGE_BORROWERS );
}
public static function handle_export() {
if ( ! WPIMLend::can_manage() ) {
wp_die( esc_html__( 'You are not allowed to do that.', 'borrowdesk' ), 403 );
}
check_admin_referer( 'wpim_lend_export' );
WPIMLend_CSV::export_loans( sanitize_key( self::get( 'view', 'open' ) ) );
exit;
}
/* ================================================================== settings */
public static function page_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$s = WPIMLend_Settings::all();
global $wpdb;
$categories = $wpdb->get_results( 'SELECT category_id, category_name FROM ' . $wpdb->prefix . 'wpinventory_category ORDER BY category_name' ); // phpcs:ignore
?>