uc_shipping.module 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. <?php
  2. /**
  3. * @file
  4. * Organizes ordered products into packages and sets them up for shipment.
  5. * Shipping method modules may add functionality to generate shipping labels
  6. * and tracking numbers.
  7. */
  8. /**
  9. * Implements hook_help().
  10. */
  11. function uc_shipping_help($path, $arg) {
  12. switch ($path) {
  13. case 'admin/store/orders/%/packages/new':
  14. return '<p>' . t('Organize products into packages. Package numbers in multiple shipping types are of the first shipping type they appear in. All packages are given a unique ID when they are saved. Choose the default package "Sep." to automatically create a package for each of the selected quantity of products in that row.') . '</p>';
  15. break;
  16. }
  17. }
  18. /**
  19. * Implements hook_menu().
  20. */
  21. function uc_shipping_menu() {
  22. $items = array();
  23. $items['admin/store/orders/%uc_order/packages'] = array(
  24. 'title' => 'Packages',
  25. 'page callback' => 'uc_shipping_order_packages',
  26. 'page arguments' => array(3),
  27. 'access callback' => 'uc_shipping_order_access',
  28. 'access arguments' => array(3),
  29. 'weight' => 6,
  30. 'type' => MENU_LOCAL_TASK,
  31. 'file' => 'uc_shipping.admin.inc',
  32. );
  33. $items['admin/store/orders/%uc_order/packages/new'] = array(
  34. 'title' => 'Create packages',
  35. 'page callback' => 'drupal_get_form',
  36. 'page arguments' => array('uc_shipping_new_package', 3),
  37. 'access callback' => 'uc_shipping_order_access',
  38. 'access arguments' => array(3),
  39. 'type' => MENU_LOCAL_ACTION,
  40. 'file' => 'uc_shipping.admin.inc',
  41. );
  42. $items['admin/store/orders/%uc_order/packages/%uc_shipping_package/edit'] = array(
  43. 'title' => 'Edit package',
  44. 'page callback' => 'drupal_get_form',
  45. 'page arguments' => array('uc_shipping_package_edit', 3, 5),
  46. 'access callback' => 'uc_shipping_order_access',
  47. 'access arguments' => array(3),
  48. 'file' => 'uc_shipping.admin.inc',
  49. );
  50. $items['admin/store/orders/%uc_order/packages/%uc_shipping_package/cancel'] = array(
  51. 'title' => 'Cancel package shipment',
  52. 'page callback' => 'drupal_get_form',
  53. 'page arguments' => array('uc_shipping_package_cancel_confirm', 3, 5),
  54. 'access callback' => 'uc_shipping_order_access',
  55. 'access arguments' => array(3),
  56. 'file' => 'uc_shipping.admin.inc',
  57. );
  58. $items['admin/store/orders/%uc_order/packages/%uc_shipping_package/delete'] = array(
  59. 'title' => 'Delete package',
  60. 'page callback' => 'drupal_get_form',
  61. 'page arguments' => array('uc_shipping_package_delete_confirm', 3, 5),
  62. 'access callback' => 'uc_shipping_order_access',
  63. 'access arguments' => array(3),
  64. 'file' => 'uc_shipping.admin.inc',
  65. );
  66. $items['admin/store/orders/%uc_order/shipments'] = array(
  67. 'title' => 'Shipments',
  68. 'page callback' => 'uc_shipping_order_shipments',
  69. 'page arguments' => array(3),
  70. 'access callback' => 'uc_shipping_order_access',
  71. 'access arguments' => array(3),
  72. 'weight' => 7,
  73. 'type' => MENU_LOCAL_TASK,
  74. 'file' => 'uc_shipping.admin.inc',
  75. );
  76. $items['admin/store/orders/%uc_order/shipments/new'] = array(
  77. 'title' => 'New shipment',
  78. 'page callback' => 'drupal_get_form',
  79. 'page arguments' => array('uc_shipping_new_shipment', 3),
  80. 'access callback' => 'uc_shipping_new_shipment_access',
  81. 'access arguments' => array(3),
  82. 'type' => MENU_LOCAL_ACTION,
  83. 'file' => 'uc_shipping.admin.inc',
  84. );
  85. $items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment'] = array(
  86. 'title callback' => 'uc_shipping_shipment_page_title',
  87. 'title arguments' => array(5),
  88. 'page callback' => 'uc_shipping_shipment_view',
  89. 'page arguments' => array(3, 5),
  90. 'access callback' => 'uc_shipping_order_access',
  91. 'access arguments' => array(3),
  92. 'file' => 'uc_shipping.admin.inc',
  93. );
  94. $items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/view'] = array(
  95. 'title' => 'View',
  96. 'weight' => -5,
  97. 'type' => MENU_DEFAULT_LOCAL_TASK,
  98. );
  99. $items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/edit'] = array(
  100. 'title' => 'Edit',
  101. 'page callback' => 'drupal_get_form',
  102. 'page arguments' => array('uc_shipping_shipment_edit', 3, 5),
  103. 'access callback' => 'uc_shipping_order_access',
  104. 'access arguments' => array(3),
  105. 'weight' => -1,
  106. 'type' => MENU_LOCAL_TASK,
  107. 'file' => 'uc_shipping.admin.inc',
  108. );
  109. $items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/print'] = array(
  110. 'title' => 'Print',
  111. 'page callback' => 'uc_shipping_shipment_print',
  112. 'page arguments' => array(3, 5),
  113. 'access callback' => 'uc_shipping_order_access',
  114. 'access arguments' => array(3),
  115. 'type' => MENU_LOCAL_TASK,
  116. 'file' => 'uc_shipping.admin.inc',
  117. );
  118. $items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/packing_slip'] = array(
  119. 'title' => 'Packing slip',
  120. 'page callback' => 'uc_shipping_shipment_print',
  121. 'page arguments' => array(3, 5, FALSE),
  122. 'access callback' => 'uc_shipping_order_access',
  123. 'access arguments' => array(3),
  124. 'type' => MENU_LOCAL_TASK,
  125. 'file' => 'uc_shipping.admin.inc',
  126. );
  127. $items['admin/store/orders/%uc_order/shipments/%uc_shipping_shipment/delete'] = array(
  128. 'title' => 'Delete shipment',
  129. 'page callback' => 'drupal_get_form',
  130. 'page arguments' => array('uc_shipping_shipment_delete_confirm', 3, 5),
  131. 'access callback' => 'uc_shipping_order_access',
  132. 'access arguments' => array(3),
  133. 'file' => 'uc_shipping.admin.inc',
  134. );
  135. $items['admin/store/orders/%uc_order/ship'] = array(
  136. 'title' => 'Ship packages',
  137. 'page callback' => 'uc_shipping_make_shipment',
  138. 'page arguments' => array(3),
  139. 'access callback' => 'uc_shipping_order_access',
  140. 'access arguments' => array(3),
  141. 'file' => 'uc_shipping.admin.inc',
  142. );
  143. return $items;
  144. }
  145. /**
  146. * Title callback for admin/store/orders/%/shipments/%.
  147. */
  148. function uc_shipping_shipment_page_title($shipment) {
  149. return t('Shipment !id', array('!id' => $shipment->sid));
  150. }
  151. /**
  152. * Ensures access to the Shipments tab.
  153. */
  154. function uc_shipping_order_access($order) {
  155. return user_access('fulfill orders') && uc_order_is_shippable($order);
  156. }
  157. /**
  158. * Access callback for the new shipment page.
  159. */
  160. function uc_shipping_new_shipment_access($order) {
  161. return uc_shipping_order_access($order) && db_query("SELECT COUNT(*) FROM {uc_packages} WHERE order_id = :id AND sid IS NULL", array(':id' => $order->order_id))->fetchField();
  162. }
  163. /**
  164. * Implements hook_menu_local_tasks_alter().
  165. */
  166. function uc_shipping_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  167. if ($root_path == 'admin/store/orders/%/shipments') {
  168. $order = $router_item['page_arguments'][0];
  169. $item = menu_get_item('admin/store/orders/' . $order->order_id . '/packages/new');
  170. if ($item['access']) {
  171. $data['actions']['output'][] = array(
  172. '#theme' => 'menu_local_action',
  173. '#link' => $item,
  174. );
  175. }
  176. }
  177. }
  178. /**
  179. * Implements hook_admin_paths().
  180. */
  181. function uc_shipping_admin_paths() {
  182. return array(
  183. // Don't show packing slips with the admin theme, overlay, etc.
  184. 'admin/store/orders/*/shipments/*/print' => FALSE,
  185. 'admin/store/orders/*/shipments/*/packing_slip' => FALSE,
  186. );
  187. }
  188. /**
  189. * Implements hook_permission().
  190. */
  191. function uc_shipping_permission() {
  192. return array(
  193. 'fulfill orders' => array(
  194. 'title' => t('Fulfill orders'),
  195. ),
  196. );
  197. }
  198. /**
  199. * Implements hook_theme().
  200. */
  201. function uc_shipping_theme() {
  202. return array(
  203. 'uc_shipping_new_package_fieldset' => array(
  204. 'render element' => 'fieldset',
  205. 'file' => 'uc_shipping.admin.inc',
  206. ),
  207. 'uc_shipping_edit_package_fieldset' => array(
  208. 'render element' => 'fieldset',
  209. 'file' => 'uc_shipping.admin.inc',
  210. ),
  211. 'uc_shipping_new_shipment' => array(
  212. 'render element' => 'form',
  213. 'file' => 'uc_shipping.admin.inc',
  214. ),
  215. 'uc_shipping_shipment_print' => array(
  216. 'variables' => array('order' => NULL, 'shipment' => NULL, 'labels' => TRUE),
  217. 'file' => 'uc_shipping.admin.inc',
  218. ),
  219. 'uc_packing_slip' => array(
  220. 'variables' => array('order' => NULL, 'shipment' => NULL),
  221. 'template' => 'uc-packing-slip',
  222. ),
  223. 'uc_packing_slip_page' => array(
  224. 'variables' => array('content' => NULL),
  225. 'template' => 'uc-packing-slip-page',
  226. ),
  227. );
  228. }
  229. /**
  230. * Preprocess function to make tokens available in the packing slip template.
  231. *
  232. * @see uc-packing-slip.tpl.php
  233. */
  234. function template_preprocess_uc_packing_slip(&$variables) {
  235. $tokens = token_generate('site', drupal_map_assoc(array('logo')));
  236. $variables['site_logo'] = isset($tokens['logo']) ? $tokens['logo'] : '';
  237. $tokens = token_generate('store', drupal_map_assoc(array('name', 'address', 'phone')));
  238. $variables['store_name'] = $tokens['name'];
  239. $variables['store_address'] = $tokens['address'];
  240. $variables['store_phone'] = $tokens['phone'];
  241. $order = $variables['order'];
  242. $variables['order_link'] = l($order->order_id, url('user/' . $order->uid . '/orders/' . $order->order_id, array('absolute' => TRUE)));
  243. $variables['order_email'] = check_plain($order->primary_email);
  244. $variables['billing_address'] = uc_order_address($order, 'billing');
  245. $variables['billing_phone'] = check_plain($order->billing_phone);
  246. $variables['shipping_address'] = uc_order_address($order, 'delivery');
  247. $variables['shipping_phone'] = check_plain($order->delivery_phone);
  248. if (module_exists('uc_payment')) {
  249. $payment_method = _uc_payment_method_data($order->payment_method, 'review');
  250. if (empty($payment_method)) {
  251. $payment_method = _uc_payment_method_data($order->payment_method, 'name');
  252. }
  253. $variables['payment_method'] = $payment_method;
  254. }
  255. else {
  256. $variables['payment_method'] = '';
  257. }
  258. $shipment = $variables['shipment'];
  259. $variables['carrier'] = check_plain($shipment->carrier);
  260. $variables['tracking_number'] = check_plain($shipment->tracking_number);
  261. $variables['packages'] = $shipment->packages;
  262. }
  263. /**
  264. * Preprocesses a printable packing slip page.
  265. *
  266. * @see uc-packing-slip-page.tpl.php
  267. */
  268. function template_preprocess_uc_packing_slip_page(&$variables) {
  269. $language = isset($GLOBALS['language']) ? $GLOBALS['language'] : language_default();
  270. $variables['language'] = $language;
  271. $variables['language']->dir = $language->direction ? 'rtl' : 'ltr';
  272. }
  273. /**
  274. * Implements hook_uc_order_pane().
  275. */
  276. function uc_shipping_uc_order_pane() {
  277. $panes['packages'] = array(
  278. 'callback' => 'uc_shipping_order_pane_packages',
  279. 'title' => t('Tracking numbers'),
  280. 'desc' => t('Display tracking numbers of shipped packages.'),
  281. 'class' => 'pos-left',
  282. 'weight' => 7,
  283. 'show' => array('view', 'invoice', 'customer'),
  284. );
  285. return $panes;
  286. }
  287. /**
  288. * Implements hook_uc_order_actions().
  289. */
  290. function uc_shipping_uc_order_actions($order) {
  291. $actions = array();
  292. if (user_access('fulfill orders')) {
  293. $result = db_query("SELECT COUNT(nid) FROM {uc_order_products} WHERE order_id = :id AND data LIKE :data", array(':id' => $order->order_id, ':data' => '%s:9:\"shippable\";s:1:\"1\";%'));
  294. if ($result->fetchField()) {
  295. $title = t('Package order !order_id products.', array('!order_id' => $order->order_id));
  296. $actions[] = array(
  297. 'name' => t('Package'),
  298. 'url' => 'admin/store/orders/' . $order->order_id . '/packages',
  299. 'icon' => theme('image', array('path' => drupal_get_path('module', 'uc_shipping') . '/images/package.gif', 'alt' => $title)),
  300. 'title' => $title,
  301. );
  302. $result = db_query("SELECT COUNT(package_id) FROM {uc_packages} WHERE order_id = :id", array(':id' => $order->order_id));
  303. if ($result->fetchField()) {
  304. $title = t('Ship order !order_id packages.', array('!order_id' => $order->order_id));
  305. $actions[] = array(
  306. 'name' => t('Ship'),
  307. 'url' => 'admin/store/orders/' . $order->order_id . '/shipments',
  308. 'icon' => theme('image', array('path' => drupal_get_path('module', 'uc_shipping') . '/images/ship.gif', 'alt' => $title)),
  309. 'title' => $title,
  310. );
  311. }
  312. }
  313. }
  314. return $actions;
  315. }
  316. /**
  317. * Displays the details of a package.
  318. */
  319. function uc_shipping_package_view($package) {
  320. $shipment = uc_shipping_shipment_load($package->sid);
  321. $build = array(
  322. '#prefix' => '<div class="order-pane pos-left">',
  323. '#suffix' => '</div>',
  324. );
  325. $rows = array();
  326. $build['title'] = array(
  327. '#markup' => t('Package %id:', array('%id' => $package->package_id)),
  328. '#prefix' => '<div class="order-pane-title">',
  329. '#suffix' => '</div>',
  330. );
  331. $rows[] = array(t('Contents:'), filter_xss_admin($package->description));
  332. if ($shipment) {
  333. $methods = module_invoke_all('uc_shipping_method');
  334. if (isset($methods[$shipment->shipping_method])) {
  335. $pkg_type = $methods[$shipment->shipping_method]['ship']['pkg_types'][$package->pkg_type];
  336. }
  337. }
  338. $rows[] = array(t('Package type:'), isset($pkg_type) ? $pkg_type : check_plain($package->pkg_type));
  339. if ($package->length && $package->width && $package->height) {
  340. $rows[] = array(t('Dimensions:'), t('!l x !w x !h', array('!l' => uc_length_format($package->length), '!w' => uc_length_format($package->width), '!h' => uc_length_format($package->height))));
  341. }
  342. $rows[] = array(t('Insured value:'), array('data' => array('#theme' => 'uc_price', '#price' => $package->value)));
  343. if ($package->tracking_number) {
  344. $rows[] = array(t('Tracking number:'), check_plain($package->tracking_number));
  345. }
  346. if ($shipment && isset($package->label_image) &&
  347. file_exists($package->label_image->uri)) {
  348. $rows[] = array(t('Label:'), l(t('Click to view.'), 'admin/store/orders/' . $package->order_id . '/shipments/labels/' . $shipment->shipping_method . '/' . $package->label_image->uri));
  349. }
  350. else {
  351. $rows[] = array(t('Label:'), t('n/a'));
  352. }
  353. $build['package'] = array(
  354. '#theme' => 'table',
  355. '#rows' => $rows,
  356. 'attributes' => array('style' => 'width:auto;'),
  357. );
  358. return $build;
  359. }
  360. /**
  361. * Loads a package and its products.
  362. */
  363. function uc_shipping_package_load($package_id) {
  364. static $packages = array();
  365. if (!isset($packages[$package_id])) {
  366. $result = db_query("SELECT * FROM {uc_packages} WHERE package_id = :id", array(':id' => $package_id));
  367. if ($package = $result->fetchObject()) {
  368. $products = array();
  369. $description = '';
  370. $weight = 0;
  371. $units = variable_get('uc_weight_unit', 'lb');
  372. $addresses = array();
  373. $result = db_query("SELECT op.order_product_id, pp.qty, pp.qty * op.weight AS weight, op.weight_units, op.nid, op.title, op.model, op.price, op.data FROM {uc_packaged_products} pp LEFT JOIN {uc_order_products} op ON op.order_product_id = pp.order_product_id WHERE pp.package_id = :id ORDER BY op.order_product_id", array(':id' => $package_id));
  374. foreach ($result as $product) {
  375. $address = uc_quote_get_default_shipping_address($product->nid);
  376. // TODO: Lodge complaint that array_unique() compares as strings.
  377. if (!in_array($address, $addresses)) {
  378. $addresses[] = $address;
  379. }
  380. $description .= ', ' . $product->qty . ' x ' . $product->model;
  381. // Normalize all weights to default units.
  382. $weight += $product->weight * uc_weight_conversion($product->weight_units, $units);
  383. $product->data = unserialize($product->data);
  384. $products[$product->order_product_id] = $product;
  385. }
  386. $package->addresses = $addresses;
  387. $package->description = substr($description, 2);
  388. $package->weight = $weight;
  389. $package->weight_units = $units;
  390. $package->products = $products;
  391. if ($package->label_image && $image = file_load($package->label_image)) {
  392. $package->label_image = $image;
  393. }
  394. else {
  395. unset($package->label_image);
  396. }
  397. $packages[$package_id] = $package;
  398. }
  399. else {
  400. return FALSE;
  401. }
  402. }
  403. return $packages[$package_id];
  404. }
  405. /**
  406. * Saves a package.
  407. */
  408. function uc_shipping_package_save($package) {
  409. $package = (object)$package;
  410. if (!isset($package->package_id)) {
  411. $package->package_id = db_insert('uc_packages')
  412. ->fields(array('order_id' => $package->order_id))
  413. ->execute();
  414. }
  415. if (isset($package->products) && $package->products) {
  416. $insert = db_insert('uc_packaged_products')
  417. ->fields(array('package_id', 'order_product_id', 'qty'));
  418. foreach ($package->products as $id => $product) {
  419. $insert->values(array(
  420. 'package_id' => $package->package_id,
  421. 'order_product_id' => $id,
  422. 'qty' => $product->qty,
  423. ));
  424. $result = db_query("SELECT data FROM {uc_order_products} WHERE order_product_id = :id", array(':id' => $id));
  425. if ($order_product = $result->fetchObject()) {
  426. $order_product->data = unserialize($order_product->data);
  427. $order_product->data['package_id'] = intval($package->package_id);
  428. db_update('uc_order_products')
  429. ->fields(array('data' => serialize($order_product->data)))
  430. ->condition('order_product_id', $id)
  431. ->execute();
  432. }
  433. }
  434. db_delete('uc_packaged_products')
  435. ->condition('package_id', $package->package_id)
  436. ->execute();
  437. $insert->execute();
  438. }
  439. $fields = array(
  440. 'order_id' => $package->order_id,
  441. 'shipping_type' => $package->shipping_type,
  442. );
  443. if (isset($package->pkg_type)) {
  444. $fields['pkg_type'] = $package->pkg_type;
  445. }
  446. if (isset($package->length) && isset($package->width) && isset($package->height) && isset($package->length_units)) {
  447. $fields['length'] = $package->length;
  448. $fields['width'] = $package->width;
  449. $fields['height'] = $package->height;
  450. $fields['length_units'] = $package->length_units;
  451. }
  452. if (isset($package->value)) {
  453. $fields['value'] = $package->value;
  454. }
  455. if (isset($package->sid)) {
  456. $fields['sid'] = $package->sid;
  457. }
  458. if (isset($package->tracking_number)) {
  459. $fields['tracking_number'] = $package->tracking_number;
  460. }
  461. if (isset($package->label_image) && is_object($package->label_image)) {
  462. $fields['label_image'] = $package->label_image->fid;
  463. }
  464. db_update('uc_packages')
  465. ->fields($fields)
  466. ->condition('package_id', $package->package_id)
  467. ->execute();
  468. }
  469. /**
  470. * Deletes a package.
  471. */
  472. function uc_shipping_package_delete($package_id) {
  473. // @todo: Make these delete functions take the actual object.
  474. $package = uc_shipping_package_load($package_id);
  475. db_delete('uc_packages')
  476. ->condition('package_id', $package_id)
  477. ->execute();
  478. db_delete('uc_packaged_products')
  479. ->condition('package_id', $package_id)
  480. ->execute();
  481. if (isset($package->label_image)) {
  482. file_usage_delete($package->label_image, 'uc_shipping', 'package', $package_id);
  483. file_delete($package->label_image);
  484. }
  485. drupal_set_message(t('Package @id has been deleted.', array('@id' => $package_id)));
  486. }
  487. /**
  488. * Loads a shipment and its packages.
  489. */
  490. function uc_shipping_shipment_load($shipment_id) {
  491. $shipment = db_query("SELECT * FROM {uc_shipments} WHERE sid = :sid", array(':sid' => $shipment_id))->fetchObject();
  492. if ($shipment) {
  493. $result = db_query("SELECT package_id FROM {uc_packages} WHERE sid = :sid", array(':sid' => $shipment_id));
  494. $packages = array();
  495. foreach ($result as $package) {
  496. $packages[$package->package_id] = uc_shipping_package_load($package->package_id);
  497. }
  498. $shipment->packages = $packages;
  499. $extra = module_invoke_all('uc_shipment', 'load', $shipment);
  500. if (is_array($extra)) {
  501. foreach ($extra as $key => $value) {
  502. $shipment->$key = $value;
  503. }
  504. }
  505. }
  506. return $shipment;
  507. }
  508. /**
  509. * Saves a shipment.
  510. */
  511. function uc_shipping_shipment_save($shipment) {
  512. if (isset($shipment->origin)) {
  513. foreach ($shipment->origin as $field => $value) {
  514. $field = 'o_' . $field;
  515. $shipment->$field = $value;
  516. $fields[$field] = $value;
  517. }
  518. }
  519. if (isset($shipment->destination)) {
  520. foreach ($shipment->destination as $field => $value) {
  521. $field = 'd_' . $field;
  522. $shipment->$field = $value;
  523. $fields[$field] = $value;
  524. }
  525. }
  526. $shipment->changed = time();
  527. if (!isset($shipment->sid)) {
  528. drupal_write_record('uc_shipments', $shipment);
  529. $shipment->is_new = TRUE;
  530. }
  531. else {
  532. drupal_write_record('uc_shipments', $shipment, 'sid');
  533. $shipment->is_new = FALSE;
  534. }
  535. if (is_array($shipment->packages)) {
  536. foreach ($shipment->packages as $package) {
  537. $package->sid = $shipment->sid;
  538. // Since the products haven't changed, we take them out of the object so
  539. // that they are not deleted and re-inserted.
  540. $products = $package->products;
  541. unset($package->products);
  542. uc_shipping_package_save($package);
  543. // But they're still necessary for hook_uc_shipment(), so they're added
  544. // back in.
  545. $package->products = $products;
  546. }
  547. }
  548. module_invoke_all('uc_shipment', 'save', $shipment);
  549. $order = uc_order_load($shipment->order_id);
  550. rules_invoke_event('uc_shipment_save', $order, $shipment);
  551. }
  552. /**
  553. * Deletes a shipment.
  554. */
  555. function uc_shipping_shipment_delete($shipment_id) {
  556. $shipment = uc_shipping_shipment_load($shipment_id);
  557. db_update('uc_packages')
  558. ->fields(array(
  559. 'sid' => NULL,
  560. 'tracking_number' => NULL,
  561. 'label_image' => NULL,
  562. ))
  563. ->condition('sid', $shipment_id)
  564. ->execute();
  565. db_delete('uc_shipments')
  566. ->condition('sid', $shipment_id)
  567. ->execute();
  568. foreach ($shipment->packages as $package) {
  569. if (isset($package->label_image)) {
  570. file_delete($package->label_image);
  571. unset($package->label_image);
  572. }
  573. }
  574. module_invoke_all('uc_shipment', 'delete', $shipment);
  575. }
  576. /**
  577. * Shipping order pane callback.
  578. *
  579. * @see uc_shipping_uc_order_pane()
  580. */
  581. function uc_shipping_order_pane_packages($op, $order) {
  582. switch ($op) {
  583. case 'view':
  584. case 'customer':
  585. $tracking = array();
  586. $result = db_query("SELECT sid FROM {uc_shipments} WHERE order_id = :id", array(':id' => $order->order_id));
  587. foreach ($result as $shipment) {
  588. $shipment = uc_shipping_shipment_load($shipment->sid);
  589. if ($shipment->tracking_number) {
  590. $tracking[$shipment->carrier]['data'] = $shipment->carrier;
  591. $tracking[$shipment->carrier]['children'][] = check_plain($shipment->tracking_number);
  592. }
  593. else {
  594. foreach ($shipment->packages as $package) {
  595. if ($package->tracking_number) {
  596. $tracking[$shipment->carrier]['data'] = $shipment->carrier;
  597. $tracking[$shipment->carrier]['children'][] = check_plain($package->tracking_number);
  598. }
  599. }
  600. }
  601. }
  602. // Do not show an empty pane to customers.
  603. if ($op == 'view' || !empty($tracking)) {
  604. $build['tracking'] = array(
  605. '#theme' => 'item_list',
  606. '#items' => $tracking,
  607. );
  608. return $build;
  609. }
  610. break;
  611. }
  612. }
  613. /**
  614. * Chooses an address to fill out a form.
  615. */
  616. function uc_shipping_select_address($addresses, $onchange = '', $title = NULL) {
  617. if (!is_array($addresses) || count($addresses) == 0) {
  618. $addresses = array();
  619. }
  620. $store_address = variable_get('uc_quote_store_default_address', new UcAddress());
  621. if (!in_array($store_address, $addresses)) {
  622. $addresses[] = $store_address;
  623. }
  624. $blank = array(
  625. 'first_name' => '',
  626. 'last_name' => '',
  627. 'phone' => '',
  628. 'company' => '',
  629. 'street1' => '',
  630. 'street2' => '',
  631. 'city' => '',
  632. 'postal_code' => '',
  633. 'country' => 0,
  634. 'zone' => 0,
  635. );
  636. $options = array(drupal_json_encode($blank) => t('- Reset fields -'));
  637. foreach ($addresses as $address) {
  638. $options[drupal_json_encode($address)] = $address->company . ' ' . $address->street1 . ' ' . $address->city;
  639. }
  640. $select = array(
  641. '#type' => 'select',
  642. '#title' => is_null($title) ? t('Address book') : $title,
  643. '#options' => $options,
  644. '#default_value' => drupal_json_encode($addresses[0]),
  645. '#attributes' => array('onchange' => $onchange),
  646. );
  647. return $select;
  648. }
  649. /**
  650. * Helper function for addresses in forms.
  651. *
  652. * @ingroup forms
  653. */
  654. function uc_shipping_address_form($form, &$form_state, $addresses, $order) {
  655. drupal_add_js(drupal_get_path('module', 'uc_shipping') . '/uc_shipping.js');
  656. $form['origin'] = array(
  657. '#type' => 'fieldset',
  658. '#title' => t('Origin address'),
  659. '#collapsible' => TRUE,
  660. '#collapsed' => FALSE,
  661. '#weight' => -2,
  662. );
  663. $form['origin']['pickup_address_select'] = uc_shipping_select_address($addresses, 'apply_address(\'pickup\', this.value);', t('Saved Addresses'));
  664. $form['origin']['pickup_address_select']['#weight'] = -2;
  665. $form['origin']['pickup_email'] = uc_textfield(t('E-mail'), uc_store_email(), FALSE, NULL, 255);
  666. $form['origin']['pickup_email']['#weight'] = -1;
  667. $form['origin']['pickup_address']['#tree'] = TRUE;
  668. $form['origin']['pickup_address']['pickup_address'] = array(
  669. '#type' => 'uc_address',
  670. '#default_value' => reset($addresses),
  671. '#required' => FALSE,
  672. );
  673. $form['destination'] = array(
  674. '#type' => 'fieldset',
  675. '#title' => t('Destination address'),
  676. '#collapsible' => TRUE,
  677. '#collapsed' => FALSE,
  678. '#weight' => -1,
  679. );
  680. if (isset($form_state['values']['delivery_country'])) {
  681. $order->delivery_country = $form_state['values']['delivery_country'];
  682. }
  683. $form['destination']['delivery_email'] = uc_textfield(t('E-mail'), $order->primary_email, FALSE, NULL, 255);
  684. $form['destination']['delivery_email']['#weight'] = -1;
  685. $form['destination']['delivery_address'] = array(
  686. '#type' => 'uc_address',
  687. '#default_value' => $order,
  688. '#required' => FALSE,
  689. '#key_prefix' => 'delivery',
  690. );
  691. return $form;
  692. }
  693. /**
  694. * Implements hook_views_api().
  695. */
  696. function uc_shipping_views_api() {
  697. return array(
  698. 'api' => '2.0',
  699. 'path' => drupal_get_path('module', 'uc_shipping') . '/views',
  700. );
  701. }
  702. /**
  703. * Implements hook_date_views_tables().
  704. */
  705. function uc_shipping_date_views_tables() {
  706. return array('uc_shipments');
  707. }
  708. /**
  709. * Implements hook_date_views_fields().
  710. *
  711. * All modules that create custom fields that use the
  712. * 'views_handler_field_date' handler can provide
  713. * additional information here about the type of
  714. * date they create so the date can be used by
  715. * the Date API views date argument and date filter.
  716. */
  717. function uc_shipping_date_views_fields($field) {
  718. $values = array(
  719. // The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
  720. 'sql_type' => DATE_UNIX,
  721. // Timezone handling options: 'none', 'site', 'date', 'utc' .
  722. 'tz_handling' => 'site',
  723. // Needed only for dates that use 'date' tz_handling.
  724. 'timezone_field' => '',
  725. // Needed only for dates that use 'date' tz_handling.
  726. 'offset_field' => '',
  727. // Array of "table.field" values for related fields that should be
  728. // loaded automatically in the Views SQL.
  729. 'related_fields' => array(),
  730. // Granularity of this date field's db data.
  731. 'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
  732. );
  733. switch ($field) {
  734. case 'uc_shipments.ship_date':
  735. case 'uc_shipments.expected_delivery':
  736. case 'uc_shipments.changed':
  737. return $values;
  738. }
  739. }
  740. /**
  741. * Implements hook_uc_order().
  742. *
  743. * Prevent users from deleting orders with a shipment or package that has
  744. * a tracking number, unless the user has administrative privileges or the
  745. * "Unconditionally delete orders" permission.
  746. *
  747. * Delete packages and shipments attached to orders being deleted.
  748. */
  749. function uc_shipping_uc_order($op, $order, $arg2) {
  750. switch ($op) {
  751. case 'can_delete':
  752. // Find and check the shipments for tracking numbers.
  753. // {uc_shipments}.tracking_number is NOT NULL.
  754. $shipment_count = db_select('uc_shipments')
  755. ->condition('order_id', $order->order_id)
  756. ->condition('tracking_number', '', '<>')
  757. ->countQuery()
  758. ->execute()
  759. ->fetchField();
  760. if ($shipment_count > 0) {
  761. return FALSE;
  762. }
  763. // Find and check the packages.
  764. $package_count = db_select('uc_packages')
  765. ->condition('order_id', $order->order_id)
  766. ->isNotNull('tracking_number')
  767. ->condition('tracking_number', '', '<>')
  768. ->countQuery()
  769. ->execute()
  770. ->fetchField();
  771. if ($package_count > 0) {
  772. return FALSE;
  773. }
  774. return TRUE;
  775. break;
  776. case 'delete':
  777. // Find and delete the shipments.
  778. $shipment_ids = db_select('uc_shipments')
  779. ->fields(NULL, array('sid'))
  780. ->condition('order_id', $order->order_id)
  781. ->execute()
  782. ->fetchCol();
  783. foreach ($shipment_ids as $sid) {
  784. uc_shipping_shipment_delete($sid);
  785. }
  786. // Find and delete the packages.
  787. $package_ids = db_select('uc_packages')
  788. ->fields(NULL, array('package_id'))
  789. ->condition('order_id', $order->order_id)
  790. ->execute()
  791. ->fetchCol();
  792. foreach ($package_ids as $pid) {
  793. uc_shipping_package_delete($pid);
  794. }
  795. break;
  796. }
  797. }