Basic search API integration.
[sandbox/serialjaywalker/1195272.git] / commerce_pos.module
1 <?php
2
3 /**
4 * @file
5 * A module that defines a POS interface for Drupal Commerce
6 */
7
8 /**
9 * Implements hook_menu().
10 */
11 function commerce_pos_menu() {
12 $items = array();
13
14 //POS Screen
15 $items['admin/commerce/pos'] = array(
16 'title callback' => 'variable_get',
17 'title arguments' => array('commerce_pos_page_title', 'POS'),
18 'description' => 'Use the POS interface',
19 'access callback' => 'user_access',
20 'access arguments' => array('use commerce pos'),
21 'page callback' => 'commerce_pos_pos',
22 );
23 $items['admin/commerce/pos/drawer_open'] = array(
24 'title' => 'Drawer opening page',
25 'description' => 'Page to be retrieved when something prints.',
26 'access callback' => 'user_access',
27 'access arguments' => array('use commerce pos'),
28 'page callback' => 'commerce_pos_drawer_open',
29 'type' => MENU_CALLBACK,
30 );
31 $items['admin/commerce/config/pos'] = array(
32 'title' => 'Commerce POS Settings',
33 'description' => 'Configuration settings for the POS',
34 'access arguments' => array('administer commerce pos'),
35 'page callback' => 'drupal_get_form',
36 'page arguments' => array('commerce_pos_settings_form'),
37 );
38
39 return $items;
40 }
41
42 /**
43 * Implements hook_hook_info().
44 */
45 function commerce_pos_hook_info() {
46 $hooks = array(
47 'commerce_pos_button_info' => array(
48 'group' => 'commerce_pos',
49 ),
50 'commerce_pos_action_info' => array(
51 'group' => 'commerce_pos',
52 ),
53 'commerce_pos_build' => array(
54 'group' => 'commerce_pos',
55 ),
56 'commerce_pos_extract' => array(
57 'group' => 'commerce_pos',
58 ),
59 );
60
61 return $hooks;
62 }
63
64 /**
65 * Implements hook_permission().
66 */
67 function commerce_pos_permission() {
68 return array(
69 'use commerce pos' => array(
70 'title' => t('Use POS'),
71 ),
72 'administer commerce pos' => array(
73 'title' => t('Administer POS'),
74 ),
75 );
76 }
77
78 /**
79 * Form callback for commerce_pos_settings_form
80 */
81 function commerce_pos_settings_form($form, &$form_state) {
82 $form = array();
83
84 //Code for currency selector borrowed from commerce_currency_settings_form()
85 foreach (commerce_currencies(FALSE, TRUE) as $currency_code => $currency) {
86 $options[$currency_code] = t('@code = !name', array('@code' => $currency['code'], '@symbol' => $currency['symbol'], '!name' => $currency['name']));
87
88 if (!empty($currency['symbol'])) {
89 $options[$currency_code] .= ' - ' . check_plain($currency['symbol']);
90 }
91 }
92 $form['commerce_pos_default_currency'] = array(
93 '#type' => 'select',
94 '#title' => t('Default POS currency'),
95 '#description' => t('The currency to be used for POS transactions'),
96 '#options' => $options,
97 '#default_value' => commerce_pos_default_currency(),
98 );
99
100 return system_settings_form($form);
101 }
102
103 /**
104 * Page callback for POS page.
105 *
106 * @param $order
107 * Commerce order object to load in POS
108 */
109 function commerce_pos_pos() {
110 drupal_add_library('system', 'ui.tabs');
111 module_invoke_all('commerce_pos_preload');
112 $form = drupal_get_form('commerce_pos_pos_form');
113 $buttons = array(
114 'content' => commerce_pos_buttons(),
115 '#prefix' => '<div id = \'commerce-pos-buttons\'>',
116 '#suffix' => '</div>',
117 );
118 $panels = array(
119 '#type' => 'commerce_pos_panel_set',
120 );
121 $panels += commerce_pos_panels();
122 $result = array($form, $buttons, $panels);
123
124 return $result;
125 }
126
127 /**
128 * Implements hook_commerce_pos_preload().
129 */
130 function commerce_pos_commerce_pos_preload() {
131 drupal_add_css(drupal_get_path('module', 'commerce_pos') . '/theme/commerce_pos.css');
132 drupal_add_js(drupal_get_path('module', 'commerce_pos') . '/commerce_pos.js', array('weight' => -1));
133 drupal_add_library('system', 'ui.dialog');
134 }
135
136 /**
137 * Implements hook_help().
138 *
139 * Displays help and module information.
140 *
141 * @param $path
142 * The path for which we are displaying help
143 * @param $arg
144 * Array holding the current path
145 */
146 function commerce_pos_help($path, $arg) {
147 switch ($path) {
148 case 'admin/commerce/pos':
149 return t('The POS interface.');
150 break;
151 case 'admin/settings/commerce/pos':
152 return t('Settings for the POS');
153 break;
154 }
155 }
156
157 /**
158 * Returns the currency code of the default POS currency.
159 */
160 function commerce_pos_default_currency() {
161 return variable_get('commerce_pos_default_currency', variable_get('commerce_default_currency', 'USD'));
162 }
163
164 /**
165 * Form callback: POS form.
166 *
167 * @param $action
168 * The name of the action to load into the POS, if any.
169 * @param $arguments
170 * Arguments to be used by the action
171 *
172 * @return
173 * The form array.
174 */
175 function commerce_pos_pos_form($form, &$form_state, $action='', $arguments=NULL) {
176
177 $form = array();
178
179 $form['replace'] = array(
180 '#type' => 'markup',
181 '#prefix' => '<div id = \'commerce-pos-replace-wrapper\'>',
182 '#suffix' => '</div>',
183 );
184
185 $form['replace']['input'] = array(
186 '#type' => 'textfield',
187 '#title' => t('Input'),
188 '#weight' => -10,
189 '#size' => 16,
190 '#attributes' => array(
191 'autocomplete' => 'off',
192 'tabindex' => 1,
193 'class' => array('commerce-pos-input'),
194 ),
195 );
196
197 $form['replace']['clear'] = array(
198 '#type' => 'markup',
199 '#markup' => t('Clear'),
200 '#prefix' => '<span class = \'commerce-pos-input-clear\'>',
201 '#suffix' => '</span>',
202 '#weight' => -9.5,
203 );
204
205 $form['replace']['enter'] = array(
206 '#type' => 'markup',
207 '#markup' => t('Enter'),
208 '#prefix' => '<span class = \'commerce-pos-input-enter\'>',
209 '#suffix' => '</span>',
210 '#weight' => -9,
211 );
212
213 $form['replace']['new_action_name'] = array(
214 '#type' => 'hidden',
215 '#attributes' => array('class' => array('commerce-pos-new-action-name'),),
216 );
217
218 $form['replace']['new_action_arg'] = array(
219 '#type' => 'hidden',
220 '#attributes' => array('class' => array('commerce-pos-new-action-arg'),),
221 );
222 if(!empty($form_state['pos'])) {
223 $action_elements = module_invoke_all('commerce_pos_build', $form_state['pos']);
224 }
225 else {
226 $action_elements = module_invoke_all('commerce_pos_build');
227 }
228 if(!empty($form_state['pos'])) {
229 $action = $form_state['pos']['action'];
230
231 //TODO: rewrite this using the theme layer
232 $classes = 'commerce-pos-action-elements';
233 if (!empty($action['name'])) {
234 $build_callbacks = commerce_pos_action_callback($action, 'build');
235 if (is_array($build_callbacks)) {
236 foreach ($build_callbacks as $callback) {
237 $new_elements = $callback($form_state['pos']);
238 if (is_array($new_elements)) {
239 $action_elements += $new_elements;
240 }
241 }
242 }
243 }
244
245 $action_elements['#prefix'] = '<div class = \'' . $classes . '\'>';
246 $action_elements['#suffix'] = '</div>';
247 $form['replace'][] = $action_elements;
248 }
249
250 $form['#submit'][] = 'commerce_pos_pos_form_submit';
251 $form['#validate'][] = 'commerce_pos_pos_form_validate';
252
253
254 $form['submit'] = array(
255 '#type' => 'submit',
256 '#value' => t('Enter'),
257 '#ajax' => array(
258 'wrapper' => 'commerce-pos-replace-wrapper',
259 'callback' => 'commerce_pos_pos_replace_callback',
260 'event' => 'click',
261 'keypress' => TRUE,
262 ),
263 '#attributes' => array('class' => array('commerce-pos-ajax-trigger')),
264 );
265
266 //Destroy POS variables. Everything we needed should be stored in the form array.
267 unset($form_state['pos']);
268
269 return $form;
270 }
271
272 /**
273 * AJAX callback for POS form
274 */
275 function commerce_pos_pos_replace_callback($form, &$form_state) {
276 return $form['replace'];
277 }
278
279 /**
280 * Validate callback for commerce_pos_pos_form().
281 */
282 function commerce_pos_pos_form_validate($form, &$form_state) {
283 //Parse the input to choose an action.
284 $form_state['pos'] = array();
285 $action = _commerce_pos_select_action($form, $form_state);
286 //Now get what we need from the form.
287 $variables = $form_state['pos'];
288 $values = $form_state['values'];
289 $variables['input'] = $values['input'];
290 $variables['action'] = $action;
291
292 //Allow modules to set some values even when their action isn't active.
293 foreach(module_implements('commerce_pos_extract') as $module) {
294 $hook = $module . '_commerce_pos_extract';
295 $hook($variables, $values);
296 }
297
298 $extract_callbacks = commerce_pos_action_callback($action, 'extract');
299 if($extract_callbacks !== FALSE) {
300 foreach($extract_callbacks as $callback) {
301 $callback($variables, $values);
302 }
303 }
304
305 //Check access.
306 $access_callbacks = commerce_pos_action_callback($action, 'access');
307 //Set an error if any callback denies access.
308 if ($access_callbacks != FALSE) {
309 foreach($access_callbacks as $callback) {
310 if (!$callback($variables)) {
311 form_set_error('input', t('You do not have permission to do that.'));
312 return;
313 }
314 }
315 }
316 //Finally, check the action's validation callbacks.
317 $count = 0;
318 $validate_callbacks = commerce_pos_action_callback($action, 'validate');
319 if(is_array($validate_callbacks)) {
320 foreach ($validate_callbacks as $callback) {
321 $errors = $callback($variables);
322 if (is_array($errors)) {
323 foreach ($errors as $error) {
324 form_set_error('input', $error);
325 ++$count;
326 }
327 }
328 }
329 }
330 if (!$count) {
331 $form_state['pos'] = $variables;
332 }
333 }
334
335 /**
336 * Submit callback for commerce_pos_pos_form().
337 */
338 function commerce_pos_pos_form_submit($form, &$form_state) {
339 $form_state['rebuild'] = TRUE;
340 $form_state['input'] = array();
341
342 $variables = $form_state['pos'];
343 $action = $variables['action'];
344
345 $execute_callbacks = commerce_pos_action_callback($action, 'execute');
346 if (is_array($execute_callbacks)) {
347 foreach ($execute_callbacks as $callback) {
348 $callback($variables);
349 }
350 }
351 drupal_add_js(array('commercePos' => array('actionName' => $action['name'])), 'setting');
352 $form_state['pos'] = $variables;
353 }
354
355 /**
356 * Get and alter all POS actions.
357 */
358 function commerce_pos_get_action_info() {
359 $actions = &drupal_static(__FUNCTION__);
360
361 if (empty($actions)) {
362 $actions = array();
363 foreach (module_implements('commerce_pos_action_info') as $module) {
364 $function = $module . '_commerce_pos_action_info';
365 $new_actions = $function();
366 foreach($new_actions as $key => $action) {
367 $action['name'] = $key;
368
369 $actions[$key] = $action;
370 }
371 }
372 drupal_alter('commerce_pos_action_info', $actions);
373 uasort($actions, 'drupal_sort_weight');
374 }
375
376 return $actions;
377 }
378
379 /**
380 * Get a specified action.
381 *
382 * @param $action_name
383 *
384 * @return
385 * The action array, or FALSE if no such action exists.
386 */
387 function commerce_pos_action_load($name) {
388 $actions = commerce_pos_get_action_info();
389
390 if(!empty($actions[$name])) {
391 return $actions[$name];
392 }
393 }
394
395 /**
396 * Determine which action should handle this form submission.
397 *
398 * @return
399 * The array giving the action to be used
400 */
401 function _commerce_pos_select_action($form, &$form_state) {
402 $actions = commerce_pos_get_action_info();
403 //Note that by this point, we know that this module has at very least set $form_state['pos']['input'].
404 $variables = $form_state['pos'];
405 $values = $form_state['values'];
406 if (!empty($values['action_name'])) {
407 $action = commerce_pos_action_load($values['action_name']);
408 if ($action != FALSE) {
409 return $action;
410 }
411 }
412 //Don't use elseif here, to allow for fallback in case the action couldn't load.
413 if (!empty($values['new_action_name'])) {
414 $action = commerce_pos_action_load($values['new_action_name']);
415 if ($action != FALSE) {
416 return $action;
417 }
418 }
419 //If no action has been set directly, try parsing the input.
420 foreach ($actions as $action_name => $action) {
421 $parse_callbacks = commerce_pos_action_callback($action, 'parse');
422 if($parse_callbacks !== FALSE) {
423 foreach($parse_callbacks as $callback) {
424 $variables['action'] = $action;
425 $variables['action_name'] = $action;
426 $action_text = empty($action['action text']) ? NULL : $action['action text'];
427 $result = $callback($values['input'], $action_text);
428 if ($result) {
429 //We found an action that will process this input.
430 $form_state['pos'] = $variables;
431 return $action;
432 }
433 }
434 }
435 }
436 }
437
438 /**
439 * Returns the specified callback function for an action
440 *
441 * @param $action
442 * A fully-loaded action array.
443 * @param $callback
444 * One of the following strings
445 * - 'access'
446 * - 'build'
447 * - 'execute'
448 * - 'extract'
449 * - 'parse'
450 * - 'validate'
451 *
452 * @return
453 * The array of callbacks for this action, or FALSE if no valid callback exists.
454 */
455 function commerce_pos_action_callback($action, $callback) {
456 $callbacks = isset($action['callbacks'][$callback]) ? $action['callbacks'][$callback] : NULL;
457
458 if (!is_array($callbacks)) {
459 $callbacks = array($callbacks);
460 }
461 foreach ($callbacks as $key => $function) {
462 if(!is_string($function) || !function_exists($function)) {
463 unset($callbacks[$key]);
464 }
465 }
466 if (count($callbacks)) {
467 return $callbacks;
468 }
469 else {
470 return FALSE;
471 }
472 }
473
474 /**
475 * Gets buttons generated by various modules
476 */
477 function commerce_pos_buttons() {
478 $buttons = array();
479 foreach (module_implements('commerce_pos_button_info') as $module) {
480 $function = $module . '_commerce_pos_button_info';
481 $sections = $function();
482 foreach ($sections as $section => $new_buttons) {
483 foreach($new_buttons as $key => $button) {
484 $new_buttons[$key]['#name'] = $key;
485 }
486 if (array_key_exists($section, $buttons)) {
487 $buttons[$section] = array_merge($buttons[$section], $new_buttons);
488 }
489 else {
490 $buttons[$section] = $new_buttons;
491 }
492 }
493 }
494
495 $section_info = array();
496 foreach (module_implements('commerce_pos_button_section_info') as $module) {
497 $function = $module . '_commerce_pos_button_section_info';
498 $new_section_info = $function();
499 $section_info += $new_section_info;
500 }
501 drupal_alter('commerce_pos_button_section_info', $section_info);
502
503 foreach ($buttons as $name => $section) {
504 $buttons[$name]['#type'] = 'commerce_pos_button_section';
505 $buttons[$name]['#name'] = $name;
506 if (!empty($section_info[$name])) {
507 $buttons[$name] += $section_info[$name];
508 }
509 }
510
511 drupal_alter('commerce_pos_button_info', $buttons);
512 return $buttons;
513 }
514
515
516 /**
517 * Implements hook_theme().
518 */
519 function commerce_pos_theme($existing, $type, $theme, $path) {
520 return array(
521 'commerce_pos_button' => array(
522 'render element' => 'commerce_pos_button',
523 ),
524 'commerce_pos_panel' => array(
525 'render element' => 'commerce_pos_panel',
526 ),
527 'commerce_pos_panel_set' => array(
528 'render element' => 'commerce_pos_panel_set',
529 ),
530 'commerce_pos_button_section' => array(
531 'render element' => 'commerce_pos_button_section',
532 ),
533 'commerce_pos_modal' => array(
534 'render element' => 'commerce_pos_modal',
535 ),
536 'commerce_pos_pos_form' => array(
537 'render element' => 'form',
538 ),
539 'commerce_pos_receipt' => array(
540 'render element' => 'commerce_pos_receipt',
541 ),
542 );
543 }
544
545 /**
546 * Implements hook_element_info().
547 */
548 function commerce_pos_element_info() {
549 return array(
550 'commerce_pos_panel' => array('#theme' => array('commerce_pos_panel')),
551 'commerce_pos_panel_set' => array('#theme' => array('commerce_pos_panel_set')),
552 'commerce_pos_button' => array('#theme' => array('commerce_pos_button')),
553 'commerce_pos_button_section' => array('#theme_wrappers' => array('commerce_pos_button_section')),
554 'commerce_pos_receipt' => array('#theme_wrappers' => array('commerce_pos_receipt')),
555 'commerce_pos_modal' => array(
556 '#theme_wrappers' => array('commerce_pos_modal'),
557 '#process' => array('commerce_pos_modal_process'),
558 )
559 );
560 }
561
562 /**
563 * Theme function for receipt element.
564 */
565 function theme_commerce_pos_receipt(&$variables) {
566 $element = $variables['commerce_pos_receipt'];
567 $attributes = (array) $element['#attributes'];
568 $attributes['class'] = (array) $attributes['class'];
569
570 $attributes['class'] += array('commerce-pos-receipt');
571
572 if (!empty($element['#unstyled'])) {
573 $attributes['class'][]= 'commerce-pos-receipt-unstyled';
574 }
575
576 $output = '<div class = \'commerce-pos-receipt-wrapper\'><div ' . drupal_attributes($attributes) . '>';
577
578 foreach( array('#header', '#time', '#children', '#footer') as $key) {
579 if (!empty($element[$key])) {
580 $output .= $element[$key];
581 }
582 }
583
584 $output .= '</div></div>';
585 return $output;
586 }
587
588 /**
589 * Theme function for commerce_pos_button_section element().
590 */
591 function theme_commerce_pos_button_section($variables) {
592 $element = $variables['commerce_pos_button_section'];
593 $attributes = array(
594 'class' => array(
595 'commerce-pos-button-section',
596 'commerce-pos-button-section-' . strtr($element['#name'], '_', '-'),
597 ),
598 );
599 $prefix = '<div ' . drupal_attributes($attributes) . '>';
600 $suffix = '</div>';
601 return $prefix . $element['#children'] . $suffix;
602 }
603
604 /**
605 * Theme function for the commerce_pos_button element.
606 * @todo: move all the processing to a better place
607 */
608 function theme_commerce_pos_button($variables) {
609 drupal_add_library('system', 'ui.button');
610 $element = $variables['commerce_pos_button'];
611 $attributes = empty($element['#attributes']) ? array() : $element['#attributes'];
612
613 $attributes['class'] = empty($attributes['class']) ? array() : $attributes['class'];
614 $attributes['class'] += array(
615 'commerce-pos-button',
616 'commerce-pos-button-' . strtr($element['#name'], '_', '-')
617 );
618 if (isset($element['#submit_form']) && $element['#submit_form']) {
619 $attributes['class'][] = 'commerce-pos-button-submit';
620 }
621 $output = '<div ' . drupal_attributes($attributes) . ' >';
622 if (isset($element['#replace_text'])) {
623 $output .= '<span class = \'commerce-pos-button-replace-text\' >'
624 . $element['#replace_text'] . '</span>';
625 }
626 if (isset($element['#append_text'])) {
627 $output .= '<span class = \'commerce-pos-button-append-text\' >'
628 . $element['#append_text'] . '</span>';
629 }
630 if (isset($element['#title'])) {
631 $output .= '<span class = \'commerce-pos-button-title\' >'
632 . $element['#title'] . '</span>';
633 }
634 if(isset($element['#action'])) {
635 $output .= '<span class = \'commerce-pos-button-action\' >'
636 . $element['#action'] . '</span>';
637 }
638 $output .= '</div>';
639
640 return $output;
641 }
642
643 /**
644 * Theme function for commerce_pos_modal element.
645 */
646 function theme_commerce_pos_modal($variables) {
647 drupal_add_library('system', 'ui.dialog');
648 $element = $variables['commerce_pos_modal'];
649 $attributes = array(
650 'class' => array(
651 'commerce-pos-modal',
652 ),
653 );
654 if($element['#visible']) {
655 $attributes['class'][] = 'commerce-pos-modal-visible';
656 }
657 $prefix = '<div ' . drupal_attributes($attributes) . '>';
658 $suffix = '</div>';
659 return $prefix . $element['#children'] . $suffix;
660 }
661
662 /**
663 * Process callback for commerce_pos_modal element
664 */
665 function commerce_pos_modal_process($element, $form_state, $complete_form) {
666 $element[] = array(
667 '#type' => 'submit',
668 '#value' => $element['#submit_text'],
669 '#ajax' => array(
670 'wrapper' => 'commerce-pos-replace-wrapper',
671 'callback' => 'commerce_pos_pos_replace_callback',
672 'event' => 'click',
673 'keypress' => TRUE,
674 ),
675 );
676
677 if(!empty($element['#cancel']) && $element['#cancel']) {
678 $element[] = array(
679 '#type' => 'submit',
680 '#value' => t('Cancel'),
681 '#ajax' => array(
682 'wrapper' => 'commerce-pos-replace-wrapper',
683 'callback' => 'commerce_pos_pos_replace_callback',
684 'event' => 'click',
685 'keypress' => TRUE,
686 ),
687 );
688 }
689 return $element;
690 }
691
692 /**
693 * Implements hook_library().
694 */
695 function commerce_pos_library() {
696 return array(
697 'printelement' => array(
698 'title' => 'jQuery Print Element',
699 'website' => 'http://plugins.jquery.com/project/printElement',
700 'version' => '1.1',
701 'js' => array(
702 drupal_get_path('module', 'commerce_pos') . '/jquery.printElement.js' => array(),
703 ),
704 ),
705 );
706 }
707
708 /**
709 * Preprocess function for the commerce_pos_pos_form form.
710 * @param unknown_type $variables
711 */
712 function template_preprocess_commerce_pos_pos_form(&$variables) {
713 //Add CSS/JS.
714 drupal_add_css(drupal_get_path('module', 'commerce_pos') . '/theme/commerce_pos.css');
715 drupal_add_js(drupal_get_path('module', 'commerce_pos') . '/commerce_pos.js', array('weight' => -1));
716 drupal_add_library('commerce_pos', 'printelement');
717 }
718
719 /**
720 * Theme function for commerce_pos_pos_form.
721 */
722 function theme_commerce_pos_pos_form($variables) {
723 $form = $variables['form'];
724 $prefix = '<div id =\'commerce-pos-pos-form-wrapper\'>';
725 $suffix = '</div>';
726 return $prefix . drupal_render_children($form) . $suffix;
727 }
728
729 /**
730 * Theme function for commerce_pos_panel element.
731 */
732 function theme_commerce_pos_panel($variables) {
733 $element = $variables['commerce_pos_panel'];
734 $attributes = empty($element['#attributes']) ? array('class' => array('commerce-pos-panel')) : $element['#attributes'] + array('commerce-pos-panel');
735
736 $output = '<div id = \'commerce-pos-panel-' . $element['#name'] . '\' ' . drupal_attributes($attributes) . '>';
737 $output .= drupal_render_children($element);
738 $output .= '</div>';
739 return $output;
740 }
741
742
743 /**
744 * Preprocess function for a set of panels.
745 */
746 function template_preprocess_commerce_pos_panel_set(&$variables) {
747 $element = $variables['commerce_pos_panel_set'];
748 foreach (element_children($element) as $key) {
749 $child = $element[$key];
750 if (!empty($child['#type']) && $child['#type'] == 'commerce_pos_panel') {
751 $id = 'commerce-pos-panel-' . $child['#name'];
752 $element['#id'] = $id;
753 $element['#panels'][$id] = $child['#title'];
754 }
755 }
756 $variables['commerce_pos_panel_set'] = $element;
757 }
758
759 /**
760 * Theme function for a set of panels.
761 */
762 function theme_commerce_pos_panel_set($variables) {
763 $element = $variables['commerce_pos_panel_set'];
764 $output = '<div class = \'commerce-pos-panel-set\'><ul>';
765
766 if (is_array($element['#panels'])) {
767 foreach($element['#panels'] as $id => $title) {
768 $output .= "<li><a href = '#$id'>$title</a></li>";
769 }
770 }
771
772 $output .= '</ul>' . drupal_render_children($element) . '</div>';
773
774 return $output;
775 }
776
777 /**
778 * Implements hook_commerce_pos_button_info().
779 */
780 function commerce_pos_commerce_pos_button_info() {
781 //Here we'll just generate a number pad with * and .
782 $buttons = array();
783 for ($i = 0; $i <=9; ++$i) {
784 $buttons['num_pad'][$i] = array(
785 '#title' => $i,
786 '#append_text' => $i,
787 '#weight' => $i,
788 '#type' => 'commerce_pos_button',
789 );
790 }
791 //Following phone/calculator/keyboard convention, make the 0 button last.
792 $buttons['num_pad'][0]['#weight'] = 11;
793 //Now the * and .
794 $buttons['num_pad']['qty'] = array(
795 '#title' => '*',
796 '#append_text' => '*',
797 '#weight' => 10,
798 '#type' => 'commerce_pos_button',
799 );
800 $buttons['num_pad']['dec'] = array(
801 '#title' => '.',
802 '#append_text' => '.',
803 '#weight' => 12,
804 '#type' => 'commerce_pos_button',
805 );
806
807 $actions = commerce_pos_get_action_info();
808 //Return buttons from actions with the appropriate keys set.
809 foreach ($actions as $action_name => $action) {
810 if (isset($action['button'])) {
811 $button = array(
812 '#type' => 'commerce_pos_button',
813 '#action' => $action['name'],
814 '#submit_form' => TRUE,
815 '#title' => $action['button']['title'],
816 '#weight' => empty($action['button']['weight']) ? NULL : $action['button']['weight'],
817 );
818 $buttons[$action['button']['section']][$action_name] = $button;
819 }
820 }
821 return $buttons;
822 }
823
824 /**
825 * Implements hook_commerce_pos_action_info().
826 */
827 function commerce_pos_commerce_pos_action_info() {
828 return array(
829 'commerce_pos_clear' => array(
830 'action text' => 'CL',
831 'callbacks' => array(
832 'parse' => array('commerce_pos_clear_parse'),
833 'execute' => array('commerce_pos_clear_execute'),
834 ),
835 'button' => array(
836 'title' => t('Clear'),
837 'section' => 'misc',
838 'weight' => -4,
839 ),
840 'weight' => 50,
841 ),
842 );
843 }
844
845 /**
846 * Parse callback for clear action.
847 */
848 function commerce_pos_clear_parse($input, $action_text) {
849 return ($input == $action_text);
850 }
851
852 /**
853 * Execute callback for clear command.
854 */
855 function commerce_pos_clear_execute(&$variables) {
856 $variables = array();
857 }
858
859 /**
860 * Utility function determines whether one string ends with another, useful for
861 * parse callbacks.
862 *
863 * @param $needle
864 * A string
865 * @param $haystack
866 * A string
867 *
868 * @return
869 * TRUE if $haystack ends with $needle, FALSE otherwise
870 */
871 function commerce_pos_string_ends_with($needle, $haystack) {
872 if(drupal_strlen($needle) > drupal_strlen($haystack)) {
873 return FALSE;
874 }
875 else {
876 return drupal_substr($haystack, drupal_strlen($haystack) - drupal_strlen($needle)) == $needle;
877 }
878 }
879
880 /**
881 * Get an array of module-defined panels.
882 */
883 function commerce_pos_panels() {
884 $panels = module_invoke_all('commerce_pos_panel_info');
885 drupal_alter('commerce_pos_panel_info', $panels);
886
887 $return = array();
888
889 foreach ($panels as $name => $panel) {
890 if (!empty($panel['content_callback'])) {
891 $return[$name] = call_user_func_array($panel['content_callback'], $panel['content_arguments']);
892 $return[$name]['#title'] = $panel['title'];
893 $return[$name]['#type'] = 'commerce_pos_panel';
894 $return[$name]['#name'] = commerce_pos_search_name_to_class($name);
895 $return[$name]['#weight'] = $panel['weight'];
896 }
897 }
898
899 return $return;
900 }
901
902 /**
903 * Implements hook_commerce_pos_button_section_info().
904 */
905 function commerce_pos_commerce_pos_button_section_info() {
906 return array('num_pad' => array('#weight' => -100));
907 }
908
909 /**
910 * Page callback for maintenance tasks while cash drawer is open.
911 */
912 function commerce_pos_drawer_open() {
913 module_invoke_all('commerce_pos_drawer_open');
914 return drupal_json_output(array('#markup' => t('Maintenance completed.')));
915 }