uc_store.module 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840
  1. <?php
  2. /**
  3. * @file
  4. * Contains global Ubercart functions and store administration functionality.
  5. *
  6. * The store module is a container of sorts for various helper functions used
  7. * in different parts of the Ubercart core. It also provides screens and
  8. * settings pages for use in store administration.
  9. */
  10. /**
  11. * Weight unit conversion constants, used by uc_weight_conversion().
  12. */
  13. /** Converts kilograms to kilograms. */
  14. define('KG_TO_KG', 1);
  15. /** Converts kilograms to grams. */
  16. define('KG_TO_G', 1000);
  17. /** Converts kilograms to pounds. */
  18. define('KG_TO_LB', 2.204622621849);
  19. /** Converts kilograms to ounces. */
  20. define('KG_TO_OZ', 35.27396194958);
  21. /** Converts grams to grams. */
  22. define('G_TO_G', 1);
  23. /** Converts grams to kilograms. */
  24. define('G_TO_KG', 0.001);
  25. /** Converts grams to pounds. */
  26. define('G_TO_LB', 0.002204622622);
  27. /** Converts grams to ounces. */
  28. define('G_TO_OZ', 0.03527396195);
  29. /** Converts pounds to pounds. */
  30. define('LB_TO_LB', 1);
  31. /** Converts pounds to ounces. */
  32. define('LB_TO_OZ', 16);
  33. /** Converts pounds to kilograms. */
  34. define('LB_TO_KG', 0.45359237);
  35. /** Converts pounds to grams. */
  36. define('LB_TO_G', 453.59237);
  37. /** Converts ounces to ounces. */
  38. define('OZ_TO_OZ', 1);
  39. /** Converts ounces to pounds. */
  40. define('OZ_TO_LB', 0.0625);
  41. /** Converts ounces to kilograms. */
  42. define('OZ_TO_KG', 0.028349523);
  43. /** Converts ounces to grams. */
  44. define('OZ_TO_G', 28.349523125);
  45. /**
  46. * Length unit conversion constants, used by uc_length_conversion().
  47. */
  48. /** Converts inches to inches. */
  49. define('IN_TO_IN', 1);
  50. /** Converts inches to feet. */
  51. define('IN_TO_FT', 0.083333333333);
  52. /** Converts inches to centimeters. */
  53. define('IN_TO_CM', 2.54);
  54. /** Converts inches to millimeters. */
  55. define('IN_TO_MM', 25.4);
  56. /** Converts feet to feet. */
  57. define('FT_TO_FT', 1);
  58. /** Converts feet to inches. */
  59. define('FT_TO_IN', 12);
  60. /** Converts feet to centimeters. */
  61. define('FT_TO_CM', 30.48);
  62. /** Converts feet to millimeters. */
  63. define('FT_TO_MM', 304.8);
  64. /** Converts centimeters to centimeters. */
  65. define('CM_TO_CM', 1);
  66. /** Converts centimeters to inches. */
  67. define('CM_TO_IN', 0.393700787402);
  68. /** Converts centimeters to feet. */
  69. define('CM_TO_FT', 0.03280839895);
  70. /** Converts centimeters to millimeters. */
  71. define('CM_TO_MM', 10);
  72. /** Converts millimeters to millimeters. */
  73. define('MM_TO_MM', 1);
  74. /** Converts millimeters to inches. */
  75. define('MM_TO_IN', 0.03937007874);
  76. /** Converts millimeters to feet. */
  77. define('MM_TO_FT', 0.003280839895);
  78. /** Converts millimeters to centimeters. */
  79. define('MM_TO_CM', 0.1);
  80. /**
  81. * Implements hook_menu().
  82. */
  83. function uc_store_menu() {
  84. $items = array();
  85. $items['admin/store'] = array(
  86. 'title' => 'Store',
  87. 'description' => 'Administer orders, products, customers, store settings, etc.',
  88. 'page callback' => 'uc_store_admin',
  89. 'access callback' => 'uc_store_admin_access',
  90. 'weight' => -12,
  91. 'file' => 'uc_store.admin.inc',
  92. );
  93. $items['admin/store/reports'] = array(
  94. 'title' => 'Reports',
  95. 'description' => 'Browse various store reports.',
  96. 'page callback' => 'uc_store_reports',
  97. 'access arguments' => array('view reports'),
  98. 'weight' => 2,
  99. 'file' => 'uc_store.admin.inc',
  100. 'position' => 'right',
  101. );
  102. $items['admin/store/settings'] = array(
  103. 'title' => 'Configuration',
  104. 'description' => 'Adjust configuration settings for Ubercart.',
  105. 'page callback' => 'uc_store_configuration_page',
  106. 'access arguments' => array('administer store'),
  107. 'weight' => 6,
  108. 'file' => 'uc_store.admin.inc',
  109. 'position' => 'right',
  110. );
  111. $items['admin/store/settings/countries'] = array(
  112. 'title' => 'Countries and addresses',
  113. 'description' => 'Manage available countries and configure address formats.',
  114. 'page callback' => 'drupal_get_form',
  115. 'page arguments' => array('uc_country_import_form'),
  116. 'access arguments' => array('administer store'),
  117. 'file' => 'uc_store.countries.inc',
  118. );
  119. $items['admin/store/settings/countries/import'] = array(
  120. 'title' => 'Countries',
  121. 'description' => 'Import and manage countries.',
  122. 'access arguments' => array('administer store'),
  123. 'type' => MENU_DEFAULT_LOCAL_TASK,
  124. 'file' => 'uc_store.countries.inc',
  125. );
  126. $items['admin/store/settings/countries/fields'] = array(
  127. 'title' => 'Address fields',
  128. 'description' => 'Edit the address field settings.',
  129. 'page callback' => 'drupal_get_form',
  130. 'page arguments' => array('uc_store_address_fields_form'),
  131. 'access arguments' => array('administer store'),
  132. 'type' => MENU_LOCAL_TASK,
  133. 'weight' => 1,
  134. 'file' => 'uc_store.admin.inc',
  135. );
  136. $items['admin/store/settings/countries/formats'] = array(
  137. 'title' => 'Address formats',
  138. 'description' => 'Edit country specific address format settings.',
  139. 'page callback' => 'drupal_get_form',
  140. 'page arguments' => array('uc_country_formats_form'),
  141. 'access arguments' => array('administer store'),
  142. 'type' => MENU_LOCAL_TASK,
  143. 'weight' => 1,
  144. 'file' => 'uc_store.countries.inc',
  145. );
  146. $items['admin/store/settings/store'] = array(
  147. 'title' => 'Store',
  148. 'description' => 'Configure basic store settings.',
  149. 'page callback' => 'drupal_get_form',
  150. 'page arguments' => array('uc_store_settings_form'),
  151. 'access arguments' => array('administer store'),
  152. 'file' => 'uc_store.admin.inc',
  153. 'weight' => -1,
  154. );
  155. $items['admin/store/settings/countries/%/disable'] = array(
  156. 'title' => 'Disable a country',
  157. 'description' => 'Disable a country from use.',
  158. 'page callback' => '_uc_country_perform_country_action',
  159. 'page arguments' => array('uc_country_disable', 4),
  160. 'access arguments' => array('administer store'),
  161. 'type' => MENU_CALLBACK,
  162. 'file' => 'uc_store.countries.inc',
  163. );
  164. $items['admin/store/settings/countries/%/enable'] = array(
  165. 'title' => 'Enable a country',
  166. 'description' => 'Enable a disabled country.',
  167. 'page callback' => '_uc_country_perform_country_action',
  168. 'page arguments' => array('uc_country_enable', 4),
  169. 'access arguments' => array('administer store'),
  170. 'type' => MENU_CALLBACK,
  171. 'file' => 'uc_store.countries.inc',
  172. );
  173. $items['admin/store/settings/countries/%/remove'] = array(
  174. 'title' => 'Remove a country',
  175. 'description' => 'Remove an installed country.',
  176. 'page callback' => 'drupal_get_form',
  177. 'page arguments' => array('uc_country_remove_form', 4),
  178. 'access arguments' => array('administer store'),
  179. 'file' => 'uc_store.countries.inc',
  180. );
  181. $items['admin/store/settings/countries/%/update/%'] = array(
  182. 'title' => 'Update a country',
  183. 'description' => 'Update an installed country.',
  184. 'page callback' => '_uc_country_perform_country_action',
  185. 'page arguments' => array('uc_country_update', 4, 6),
  186. 'access arguments' => array('administer store'),
  187. 'type' => MENU_CALLBACK,
  188. 'file' => 'uc_store.countries.inc',
  189. );
  190. return $items;
  191. }
  192. /**
  193. * Access callback for top-level store administration menu item.
  194. */
  195. function uc_store_admin_access() {
  196. return user_access('administer_store')
  197. || user_access('view all orders')
  198. || user_access('view customers')
  199. || user_access('administer products')
  200. || user_access('view reports');
  201. }
  202. /**
  203. * Implements hook_init().
  204. */
  205. function uc_store_init() {
  206. module_load_include('inc', 'uc_store', 'includes/tapir');
  207. global $conf;
  208. $conf['i18n_variables'][] = 'uc_store_name';
  209. }
  210. /**
  211. * Implements hook_element_info().
  212. */
  213. function uc_store_element_info() {
  214. $types = array();
  215. $types['tapir_table'] = array(
  216. '#columns' => array(),
  217. '#rows' => array(),
  218. '#tree' => TRUE,
  219. '#value' => NULL,
  220. '#pre_render' => array('tapir_gather_rows'),
  221. '#theme' => 'tapir_table',
  222. '#process' => array('ajax_process_form'),
  223. );
  224. $types['uc_address'] = array(
  225. '#input' => TRUE,
  226. '#required' => TRUE,
  227. '#process' => array('uc_store_process_address_field'),
  228. '#attributes' => array('class' => array('uc-store-address-field')),
  229. '#theme_wrappers' => array('container'),
  230. '#key_prefix' => '',
  231. '#hidden' => FALSE,
  232. );
  233. $sign_flag = variable_get('uc_sign_after_amount', FALSE);
  234. $currency_sign = variable_get('uc_currency_sign', '$');
  235. $types['uc_price'] = array(
  236. '#input' => TRUE,
  237. '#size' => 15,
  238. '#maxlength' => 15,
  239. '#autocomplete_path' => FALSE,
  240. '#process' => array('ajax_process_form'),
  241. '#element_validate' => array('uc_store_validate_number'),
  242. '#theme' => 'textfield',
  243. '#theme_wrappers' => array('form_element'),
  244. '#field_prefix' => $sign_flag ? '' : $currency_sign,
  245. '#field_suffix' => $sign_flag ? $currency_sign : '',
  246. '#allow_negative' => FALSE,
  247. '#empty_zero' => TRUE,
  248. );
  249. $types['uc_quantity'] = array(
  250. '#input' => TRUE,
  251. '#size' => 5,
  252. '#maxlength' => 6,
  253. '#required' => TRUE,
  254. '#autocomplete_path' => FALSE,
  255. '#process' => array('ajax_process_form'),
  256. '#element_validate' => array('uc_store_validate_uc_quantity'),
  257. '#theme' => 'textfield',
  258. '#theme_wrappers' => array('form_element'),
  259. '#allow_zero' => FALSE,
  260. );
  261. return $types;
  262. }
  263. /**
  264. * Element process hook for address fields.
  265. */
  266. function uc_store_process_address_field($element, $form_state) {
  267. $element['#tree'] = TRUE;
  268. $prefix = $element['#key_prefix'] ? ($element['#key_prefix'] . '_') : '';
  269. $weight = uc_store_address_field_weights();
  270. if (isset($form_state['uc_address'])) {
  271. // Use submitted Ajax values.
  272. $value = $form_state['uc_address'];
  273. }
  274. elseif (is_array($element['#value']) || is_object($element['#value'])) {
  275. // Use provided default value.
  276. $value = (array) $element['#value'];
  277. }
  278. else {
  279. $value = array();
  280. }
  281. $countries = db_query("SELECT country_id, country_name FROM {uc_countries} WHERE version > :version", array(':version' => 0))->fetchAllKeyed();
  282. foreach ($countries as $country_id => $country_name) {
  283. $countries[$country_id] = t($country_name);
  284. }
  285. natcasesort($countries);
  286. // Force the selected country to a valid one, so the zone dropdown matches.
  287. if (isset($value[$prefix . 'country']) && !isset($countries[$value[$prefix . 'country']])) {
  288. $country_keys = array_keys($countries);
  289. $value[$prefix . 'country'] = $country_keys[0];
  290. }
  291. // Iterating on the UcAddress object excludes non-public properties, which
  292. // is exactly what we want to do.
  293. $address = new UcAddress();
  294. foreach ($address as $base_field => $field_value) {
  295. $field = $prefix . $base_field;
  296. if (!isset($value[$field])) {
  297. continue;
  298. }
  299. switch ($base_field) {
  300. case 'country':
  301. $subelement = array(
  302. '#type' => 'select',
  303. '#options' => $countries,
  304. '#ajax' => array(
  305. 'callback' => 'uc_store_update_address_field_zones',
  306. 'wrapper' => 'uc-store-address-' . str_replace('_', '-', $prefix) . 'zone-wrapper',
  307. 'progress' => array(
  308. 'type' => 'throbber',
  309. ),
  310. ),
  311. '#element_validate' => array('uc_store_validate_address_field_country'),
  312. '#key_prefix' => $element['#key_prefix'],
  313. );
  314. break;
  315. case 'zone':
  316. $subelement = array(
  317. '#prefix' => '<div id="uc-store-address-' . str_replace('_', '-', $prefix) . 'zone-wrapper">',
  318. '#suffix' => '</div>',
  319. );
  320. $zones = db_query("SELECT zone_id, zone_name FROM {uc_zones} WHERE zone_country_id = :country", array(':country' => $value[$prefix . 'country']))->fetchAllKeyed();
  321. if (!empty($zones)) {
  322. natcasesort($zones);
  323. $subelement += array(
  324. '#type' => 'select',
  325. '#options' => $zones,
  326. '#empty_value' => 0,
  327. );
  328. }
  329. else {
  330. $subelement += array(
  331. '#type' => 'hidden',
  332. '#value' => 0,
  333. '#required' => FALSE,
  334. );
  335. }
  336. break;
  337. case 'postal_code':
  338. $subelement = array(
  339. '#type' => 'textfield',
  340. '#size' => 10,
  341. '#maxlength' => 10,
  342. );
  343. break;
  344. case 'phone':
  345. $subelement = array(
  346. '#type' => 'textfield',
  347. '#size' => 16,
  348. '#maxlength' => 32,
  349. );
  350. break;
  351. default:
  352. $subelement = array(
  353. '#type' => 'textfield',
  354. '#size' => 32,
  355. );
  356. }
  357. // Copy JavaScript states from the parent element.
  358. if (isset($element['#states'])) {
  359. $subelement['#states'] = $element['#states'];
  360. }
  361. // Set common values for all address fields.
  362. $label = uc_get_field_name($base_field);
  363. $element[$field] = $subelement + array(
  364. '#title' => $label ? $label : '&nbsp;',
  365. '#default_value' => $value[$field],
  366. '#parents' => array_merge(array_slice($element['#parents'], 0, -1), array($field)),
  367. '#pre_render' => array('uc_store_pre_render_address_field'),
  368. '#access' => $element['#hidden'] ? FALSE : uc_address_field_enabled($base_field),
  369. '#required' => $element['#required'] ? uc_address_field_required($base_field) : FALSE,
  370. '#weight' => (isset($weight[$base_field])) ? $weight[$base_field] : 0,
  371. );
  372. }
  373. return $element;
  374. }
  375. /**
  376. * Element validation callback for country field.
  377. *
  378. * Store the current address for use when rebuilding the form.
  379. */
  380. function uc_store_validate_address_field_country($element, &$form_state) {
  381. $address = drupal_array_get_nested_value($form_state['values'], array_slice($element['#parents'], 0, -1));
  382. $form_state['uc_address'] = isset($form_state['uc_address']) ? array_merge($form_state['uc_address'], $address) : $address;
  383. }
  384. /**
  385. * Ajax callback: updates the zone select box when the country is changed.
  386. */
  387. function uc_store_update_address_field_zones($form, &$form_state) {
  388. $element = &$form;
  389. foreach (array_slice($form_state['triggering_element']['#array_parents'], 0, -1) as $field) {
  390. $element = &$element[$field];
  391. }
  392. $prefix = empty($element['#key_prefix']) ? '' : ($element['#key_prefix'] . '_');
  393. return $element[$prefix . 'zone'];
  394. }
  395. /**
  396. * Prerenders address field elements to move the required marker when needed.
  397. */
  398. function uc_store_pre_render_address_field($element) {
  399. if (!empty($element['#required'])) {
  400. $element['#title'] = theme('form_required_marker', $element) . ' ' . $element['#title'];
  401. unset($element['#required']);
  402. }
  403. return $element;
  404. }
  405. /**
  406. * Helper function to determine the value for a uc_price form element.
  407. */
  408. function form_type_uc_price_value($element, $input = FALSE) {
  409. if ($input === FALSE && !empty($element['#default_value'])) {
  410. return uc_store_format_price_field_value($element['#default_value']);
  411. }
  412. elseif (empty($input) && empty($element['#required']) && !empty($element['#empty_zero'])) {
  413. // Empty non-required prices should be treated as zero.
  414. return 0;
  415. }
  416. }
  417. /**
  418. * Generic form element validation handler for numbers.
  419. */
  420. function uc_store_validate_number(&$element, &$form_state) {
  421. $value = $element['#value'];
  422. if ($value != '') {
  423. if (!is_numeric($value)) {
  424. form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
  425. }
  426. elseif (empty($element['#allow_negative']) && $value < 0) {
  427. form_error($element, t('%name must not be negative.', array('%name' => $element['#title'])));
  428. }
  429. }
  430. }
  431. /**
  432. * Form element validation handler for #type 'uc_quantity'.
  433. */
  434. function uc_store_validate_uc_quantity(&$element, &$form_state) {
  435. if (!preg_match('/^\d+$/', $element['#value'])) {
  436. form_error($element, t('The quantity must be a number.'));
  437. }
  438. elseif (empty($element['#allow_zero']) && !$element['#value']) {
  439. form_error($element, t('The quantity cannot be zero.'));
  440. }
  441. }
  442. /**
  443. * Implements hook_theme().
  444. */
  445. function uc_store_theme() {
  446. return array(
  447. 'uc_store_footer' => array(
  448. 'variables' => array('message' => ''),
  449. 'file' => 'uc_store.theme.inc',
  450. ),
  451. 'uc_store_address_fields_form' => array(
  452. 'render element' => 'form',
  453. 'file' => 'uc_store.admin.inc',
  454. ),
  455. 'uc_pane_sort_table' => array(
  456. 'render element' => 'form',
  457. 'file' => 'uc_store.theme.inc',
  458. ),
  459. 'tapir_table' => array(
  460. 'render element' => 'element',
  461. ),
  462. 'uc_price' => array(
  463. 'variables' => array('price' => 0, 'suffixes' => array()),
  464. 'file' => 'uc_store.theme.inc',
  465. ),
  466. 'uc_qty_label' => array(
  467. 'variables' => array(),
  468. 'file' => 'uc_store.theme.inc',
  469. ),
  470. 'uc_qty' => array(
  471. 'variables' => array('qty' => 1),
  472. 'file' => 'uc_store.theme.inc',
  473. ),
  474. 'uc_uid' => array(
  475. 'variables' => array('uid' => 0),
  476. 'file' => 'uc_store.theme.inc',
  477. ),
  478. );
  479. }
  480. /**
  481. * Implements hook_help().
  482. */
  483. function uc_store_help($path, $arg) {
  484. switch ($path) {
  485. case 'admin/help#uc_store':
  486. $output = '<h3>' . t('Ubercart') . '</h3>';
  487. $output .= '<p>' . t('Use the following links to find documentation and support:') . '</p>';
  488. $output .= '<ul>';
  489. $output .= '<li>' . l(t("Ubercart User's Guide"), 'http://www.ubercart.org/docs/user') . '</li>';
  490. $output .= '<li>' . l(t('Support Forums'), 'http://www.ubercart.org/forum') . '</li>';
  491. $output .= '</ul>';
  492. return $output;
  493. case 'admin/store/reports':
  494. $output = '<p>' . t('Various reports generated by Ubercart modules can be found here. Click the links below to view the reports.') . '</p>';
  495. if (!module_exists('uc_reports')) {
  496. $output .= '<p>' . t('To view core Ubercart statistics enable the <strong>Reports</strong> module on the <a href="!url">module administration page</a>.', array('!url' => url('admin/modules', array('fragment' => 'edit-modules-ubercart-core-optional')))) . '</p>';
  497. }
  498. return $output;
  499. }
  500. }
  501. /**
  502. * Implements hook_permission().
  503. */
  504. function uc_store_permission() {
  505. return array(
  506. 'administer store' => array(
  507. 'title' => t('Administer store'),
  508. 'restrict access' => TRUE,
  509. ),
  510. 'view reports' => array(
  511. 'title' => t('View reports'),
  512. ),
  513. );
  514. }
  515. /**
  516. * Implements hook_date_formats().
  517. */
  518. function uc_store_date_formats() {
  519. return array(
  520. array(
  521. 'type' => 'uc_store',
  522. 'format' => 'Y-m-d',
  523. 'locales' => array(),
  524. ),
  525. array(
  526. 'type' => 'uc_store',
  527. 'format' => 'm/d/Y',
  528. 'locales' => array('en-us'),
  529. ),
  530. array(
  531. 'type' => 'uc_store',
  532. 'format' => 'd/m/Y',
  533. 'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
  534. ),
  535. array(
  536. 'type' => 'uc_store',
  537. 'format' => 'Y/m/d',
  538. 'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
  539. ),
  540. array(
  541. 'type' => 'uc_store',
  542. 'format' => 'd.m.Y',
  543. 'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'),
  544. ),
  545. );
  546. }
  547. /**
  548. * Implements hook_date_format_types().
  549. */
  550. function uc_store_date_format_types() {
  551. return array('uc_store' => t('Ubercart'));
  552. }
  553. /**
  554. * Implements hook_page_alter().
  555. */
  556. function uc_store_page_alter(&$page) {
  557. $id = variable_get('uc_footer_message', 0);
  558. // Exit if the store footer is turned off.
  559. if ($id === 'none') {
  560. return;
  561. }
  562. // Figure out what page is being viewed.
  563. $path = drupal_get_normal_path($_GET['q']);
  564. $parts = explode('/', $path);
  565. // Exit if the page isn't governed by Ubercart.
  566. switch ($parts[0]) {
  567. case 'admin':
  568. // No footer on /admin or /admin/*.
  569. // But add a footer on /admin/store and /admin/store/*.
  570. if (!isset($parts[1]) || $parts[1] != 'store') {
  571. return;
  572. }
  573. break;
  574. case 'node':
  575. // No footer on /node or /node/[type]/add.
  576. // Only add a footer on /node/[nid] if that node is a product.
  577. if (count($parts) != 2 || intval($parts[1]) == 0) {
  578. return;
  579. }
  580. else {
  581. $node = node_load($parts[1]);
  582. if ($node == FALSE || !function_exists('uc_product_node_info') || !uc_product_is_product($node->type)) {
  583. return;
  584. }
  585. }
  586. break;
  587. case 'catalog':
  588. case 'cart':
  589. break;
  590. default:
  591. return;
  592. }
  593. $messages = _uc_store_footer_options();
  594. if ($id == 0) {
  595. // Pseudorandom number based on the hash of the path and the site's private
  596. // key, so messages are consistent between pages on the same site, but
  597. // different on the same pages on different sites.
  598. $id = (hexdec(substr(md5($path . drupal_get_private_key()), 0, 2)) % count($messages)) + 1;
  599. }
  600. $page['page_bottom']['ubercart_footer'] = array(
  601. '#theme' => 'uc_store_footer',
  602. '#message' => $messages[$id],
  603. );
  604. }
  605. /**
  606. * Implements hook_reviews().
  607. *
  608. * Provides code reviews for coder_review.module.
  609. */
  610. function uc_store_reviews() {
  611. $coder_reviews = array();
  612. $path = drupal_get_path('module', 'uc_store') . '/includes';
  613. $files = drupal_system_listing('/coder_review_.*\.inc$/', $path, 'filepath', 0);
  614. foreach ($files as $file) {
  615. require_once DRUPAL_ROOT . '/' . $file->uri;
  616. $function = $file->name . '_reviews';
  617. if (function_exists($function)) {
  618. if ($review = call_user_func($function)) {
  619. $coder_reviews = array_merge($coder_reviews, $review);
  620. }
  621. }
  622. }
  623. return $coder_reviews;
  624. }
  625. /**
  626. * Returns the default store footer options.
  627. */
  628. function _uc_store_footer_options() {
  629. $url = array('!url' => 'http://www.ubercart.org/');
  630. return array(
  631. 1 => t('<a href="!url">Powered by Ubercart</a>', $url),
  632. 2 => t('<a href="!url">Drupal e-commerce</a> provided by Ubercart.', $url),
  633. 3 => t('Supported by Ubercart, an <a href="!url">open source e-commerce suite</a>.', $url),
  634. 4 => t('Powered by Ubercart, the <a href="!url">free shopping cart software</a>.', $url),
  635. );
  636. }
  637. /**
  638. * Helper function for hook_entity_property_info() and hook_rules_data_info().
  639. *
  640. * Should be used by implementations of those hooks that wish to wrap address
  641. * selectors.
  642. */
  643. function uc_address_property_info() {
  644. return array(
  645. 'first_name' => array(
  646. 'type' => 'text',
  647. 'label' => t('First name'),
  648. 'description' => t('First name of the addressee.'),
  649. ),
  650. 'last_name' => array(
  651. 'type' => 'text',
  652. 'label' => t('Last name'),
  653. 'description' => t('Last name of the addressee.'),
  654. ),
  655. 'company' => array(
  656. 'type' => 'text',
  657. 'label' => t('Company'),
  658. 'description' => t('Name of the company at the address.'),
  659. ),
  660. 'street1' => array(
  661. 'type' => 'text',
  662. 'label' => t('Street line 1'),
  663. 'description' => t('First line of the street address.'),
  664. ),
  665. 'street2' => array(
  666. 'type' => 'text',
  667. 'label' => t('Street line 2'),
  668. 'description' => t('Second line of the street address.'),
  669. ),
  670. 'city' => array(
  671. 'type' => 'text',
  672. 'label' => t('City'),
  673. 'description' => t('Address city.'),
  674. ),
  675. 'zone' => array(
  676. 'type' => 'integer',
  677. 'label' => t('Zone'),
  678. 'description' => t('Address state/province/zone.'),
  679. 'options list' => 'uc_zone_option_list',
  680. ),
  681. 'postal_code' => array(
  682. 'type' => 'text',
  683. 'label' => t('Postal code'),
  684. 'description' => t('Address post code.'),
  685. ),
  686. 'country' => array(
  687. 'type' => 'integer',
  688. 'label' => t('Country'),
  689. 'description' => t('Address country.'),
  690. 'options list' => 'uc_country_option_list',
  691. ),
  692. 'phone' => array(
  693. 'type' => 'text',
  694. 'label' => t('Phone'),
  695. 'description' => t('Contact phone number.'),
  696. ),
  697. 'email' => array(
  698. 'type' => 'text',
  699. 'label' => t('Email'),
  700. 'description' => t('Contact email address.'),
  701. ),
  702. );
  703. }
  704. /**
  705. * Returns an IMG tag for a store icon. Deprecated; use theme('image') instead.
  706. *
  707. * @param $path
  708. * The Drupal path of the menu item. Atlernately may specify a filename by
  709. * passing this string as file:filename.png.
  710. * @param $small
  711. * Pass TRUE to get a link to the small version of the icon. If specifying a
  712. * filename, you should let this be FALSE.
  713. *
  714. * @return
  715. * HTML output for the image.
  716. */
  717. function uc_store_get_icon($path, $small = FALSE, $class = 'uc-store-icon', $alt = NULL) {
  718. $file = FALSE;
  719. switch ($path) {
  720. case 'admin/store':
  721. $file = 'store_monitor';
  722. break;
  723. case 'admin/store/orders':
  724. $file = 'menu_orders';
  725. break;
  726. case 'admin/store/customers':
  727. $file = 'menu_customers';
  728. break;
  729. case 'admin/store/products':
  730. $file = 'menu_products';
  731. break;
  732. case 'admin/store/reports':
  733. $file = 'menu_reports';
  734. break;
  735. case 'admin/store/settings':
  736. $file = 'menu_store_settings';
  737. break;
  738. case 'admin/store/help':
  739. $file = 'menu_help';
  740. break;
  741. }
  742. if (substr($path, 0, 5) == 'file:') {
  743. $file = substr($path, 5);
  744. }
  745. if (!$file) {
  746. // See if it's hooked in anywhere else...
  747. return '';
  748. }
  749. if ($small) {
  750. $file .= '_small';
  751. }
  752. return theme('image', array(
  753. 'path' => drupal_get_path('module', 'uc_store') . '/images/' . $file . '.gif',
  754. 'alt' => $alt,
  755. 'attributes' => array('class' => array($class)),
  756. ));
  757. }
  758. /**
  759. * Formats an amount for display with the store's currency settings.
  760. *
  761. * @param $value
  762. * The numeric value of the currency amount.
  763. * @param $sign
  764. * The currency symbol. If FALSE is given, no symbol is used. The default,
  765. * NULL, causes the variable 'uc_currency_sign' to be used, which defaults to
  766. * '$'.
  767. * @param $thou
  768. * The thousands separator character. If FALSE is given, no separator is used.
  769. * The default, NULL, causes the variable 'uc_currency_sign' to be used, which
  770. * defaults to ','.
  771. * @param $dec
  772. * The decimal separator character. If FALSE is given, confusion will abound,
  773. * because it will look 100 times bigger. The default, NULL, causes the
  774. * variable 'uc_currency_dec' to be used, which defaults to '.'.
  775. *
  776. * @return
  777. * String containing price formatted with currency symbol and separators.
  778. */
  779. function uc_currency_format($value, $sign = NULL, $thou = NULL, $dec = NULL) {
  780. if ($value === NULL) {
  781. return NULL;
  782. }
  783. $output = '';
  784. $sign_after = variable_get('uc_sign_after_amount', FALSE);
  785. $prec = variable_get('uc_currency_prec', 2);
  786. if (is_null($sign)) {
  787. $sign = variable_get('uc_currency_sign', '$');
  788. }
  789. if (is_null($thou)) {
  790. $thou = variable_get('uc_currency_thou', ',');
  791. }
  792. if (is_null($dec)) {
  793. $dec = variable_get('uc_currency_dec', '.');
  794. }
  795. // If the value is significantly less than the minimum precision, zero it.
  796. if ($prec > 0 && round(abs($value), $prec + 1) < pow(10, -$prec)) {
  797. $value = 0;
  798. }
  799. // Force the price to a positive value and add a negative sign if necessary.
  800. if ($value < 0) {
  801. $value = abs($value);
  802. $output .= '-';
  803. }
  804. // Add the currency sign first if specified.
  805. if ($sign && !$sign_after) {
  806. $output .= $sign;
  807. }
  808. // Format the number, like 1234.567 => 1,234.57
  809. $output .= number_format($value, $prec, $dec, $thou);
  810. // Add the currency sign last if specified.
  811. if ($sign && $sign_after) {
  812. $output .= $sign;
  813. }
  814. return $output;
  815. }
  816. /**
  817. * Formats a weight value for display.
  818. *
  819. * @param $value
  820. * Numerical weight value.
  821. * @param $unit
  822. * Weight unit. One of 'lb', 'oz', 'kg', or 'g', or NULL to use store
  823. * default weight units.
  824. *
  825. * @return
  826. * String containing formattted weight, including weight units.
  827. */
  828. function uc_weight_format($value, $unit = NULL) {
  829. $vars = array('!value' => $value);
  830. if (is_null($unit)) {
  831. $unit = variable_get('uc_weight_unit', 'lb');
  832. }
  833. $defaults = array(
  834. 'lb' => '!value lb.',
  835. 'oz' => '!value oz.',
  836. 'kg' => '!valuekg',
  837. 'g' => '!valueg',
  838. );
  839. $pattern = variable_get('uc_weight_format_' . $unit, $defaults[$unit]);
  840. if (strpos($pattern, '!value') === FALSE) {
  841. $pattern = $defaults[$unit];
  842. }
  843. $format = strtr($pattern, $vars);
  844. return $format;
  845. }
  846. /**
  847. * Gets the conversion ratio from one unit of weight to another.
  848. */
  849. function uc_weight_conversion($from_units, $to_units = NULL) {
  850. if (is_null($to_units)) {
  851. $to_units = variable_get('uc_weight_unit', 'lb');
  852. }
  853. $constant = strtoupper($from_units) . '_TO_' . strtoupper($to_units);
  854. if (defined($constant) && ($conversion = constant($constant)) > 0) {
  855. return $conversion;
  856. }
  857. else {
  858. return 1;
  859. }
  860. }
  861. /**
  862. * Formats a length value for display.
  863. *
  864. * @param $value
  865. * Numerical length value.
  866. * @param $unit
  867. * Length unit. One of 'ft', 'in', 'cm', or 'mm', or NULL to use store
  868. * default length units.
  869. *
  870. * @return
  871. * String containing formattted length, including length units.
  872. */
  873. function uc_length_format($value, $unit = NULL) {
  874. $vars = array('!value' => $value);
  875. if (is_null($unit)) {
  876. $unit = variable_get('uc_length_unit', 'in');
  877. }
  878. $defaults = array(
  879. 'in' => '!valuein.',
  880. 'ft' => '!valueft.',
  881. 'cm' => '!valuecm',
  882. 'mm' => '!valuemm',
  883. );
  884. $pattern = variable_get('uc_length_format_' . $unit, $defaults[$unit]);
  885. if (strpos($pattern, '!value') === FALSE) {
  886. $pattern = $defaults[$unit];
  887. }
  888. $format = strtr($pattern, $vars);
  889. return $format;
  890. }
  891. /**
  892. * Gets the conversion ratio from one unit of length to another.
  893. */
  894. function uc_length_conversion($from_units, $to_units = NULL) {
  895. if (is_null($to_units)) {
  896. $to_units = variable_get('uc_length_unit', 'in');
  897. }
  898. $constant = strtoupper($from_units) . '_TO_' . strtoupper($to_units);
  899. if (defined($constant) && ($conversion = constant($constant)) > 0) {
  900. return $conversion;
  901. }
  902. else {
  903. return 1;
  904. }
  905. }
  906. /**
  907. * Formats a date value for display.
  908. *
  909. * @param $month
  910. * Numerical month value.
  911. * @param $day
  912. * Numerical day value.
  913. * @param $year
  914. * Numerical year value.
  915. *
  916. * @return
  917. * String containing formattted date, using the 'uc_store' date format.
  918. */
  919. function uc_date_format($month, $day, $year) {
  920. $time = strtotime($month . '/' . $day . '/' . $year);
  921. return format_date($time, 'uc_store');
  922. }
  923. /**
  924. * Saves the address format for a country.
  925. */
  926. function uc_set_address_format($country_id, $format) {
  927. variable_set('uc_address_format_' . intval($country_id), $format);
  928. }
  929. /**
  930. * Formats an address for display based on a country's address format.
  931. */
  932. function uc_address_format($first_name, $last_name, $company, $street1, $street2, $city, $zone, $postal_code, $country) {
  933. $result = db_query("SELECT * FROM {uc_zones} WHERE zone_id = :id", array(':id' => $zone));
  934. if (!($zone_data = $result->fetchAssoc())) {
  935. $zone_data = array('zone_code' => t('N/A'), 'zone_name' => t('Unknown'));
  936. }
  937. $result = db_query("SELECT * FROM {uc_countries} WHERE country_id = :id", array(':id' => $country));
  938. if (!($country_data = $result->fetchAssoc())) {
  939. $country_data = array(
  940. 'country_name' => t('Unknown'),
  941. 'country_iso_code_2' => t('N/A'),
  942. 'country_iso_code_3' => t('N/A'),
  943. );
  944. }
  945. $variables = array(
  946. "\r\n" => '<br />',
  947. '!company' => check_plain($company),
  948. '!first_name' => check_plain($first_name),
  949. '!last_name' => check_plain($last_name),
  950. '!street1' => check_plain($street1),
  951. '!street2' => check_plain($street2),
  952. '!city' => check_plain($city),
  953. '!zone_code' => $zone_data['zone_code'],
  954. '!zone_name' => $zone_data['zone_name'],
  955. '!postal_code' => check_plain($postal_code),
  956. '!country_name' => t($country_data['country_name']),
  957. '!country_code2' => $country_data['country_iso_code_2'],
  958. '!country_code3' => $country_data['country_iso_code_3'],
  959. );
  960. if (uc_store_default_country() != $country) {
  961. $variables['!country_name_if'] = t($country_data['country_name']);
  962. $variables['!country_code2_if'] = $country_data['country_iso_code_2'];
  963. $variables['!country_code3_if'] = $country_data['country_iso_code_3'];
  964. }
  965. else {
  966. $variables['!country_name_if'] = '';
  967. $variables['!country_code2_if'] = '';
  968. $variables['!country_code3_if'] = '';
  969. }
  970. $format = variable_get('uc_address_format_' . $country, '');
  971. if (empty($format)) {
  972. $format = "!company\r\n!first_name !last_name\r\n!street1\r\n!street2\r\n!city, !zone_code !postal_code\r\n!country_name_if";
  973. }
  974. $address = strtr($format, $variables);
  975. $address = strtr($address, array("\n" => '<br />'));
  976. $match = array('`^<br( /)?>`', '`<br( /)?>$`', '`<br( /)?>(\s*|[\s*<br( /)?>\s*]+)<br( /)?>`', '`<br( /)?><br( /)?>`', '`<br( /)?>, N/A`');
  977. $replace = array('', '', '<br />', '<br />', '', '');
  978. $address = preg_replace($match, $replace, $address);
  979. return $address;
  980. }
  981. /**
  982. * Returns the code abbreviation for a zone based on the zone ID or name.
  983. */
  984. function uc_get_zone_code($zone = NULL) {
  985. if (empty($zone)) {
  986. return FALSE;
  987. }
  988. if (is_numeric($zone)) {
  989. $result = db_query("SELECT zone_code FROM {uc_zones} WHERE zone_id = :id", array(':id' => $zone));
  990. }
  991. else {
  992. $result = db_query("SELECT zone_code FROM {uc_zones} WHERE zone_name = :name", array(':name' => $zone));
  993. }
  994. if ($row = $result->fetchObject()) {
  995. return $row->zone_code;
  996. }
  997. return FALSE;
  998. }
  999. /**
  1000. * Returns country data based on the supplied criteria.
  1001. *
  1002. * @param $match
  1003. * An associative array of fields to match.
  1004. * @param $sort
  1005. * The field to sort by.
  1006. */
  1007. function uc_get_country_data($match = array(), $sort = 'country_name') {
  1008. $valid_fields = array('country_id', 'country_name', 'country_iso_code_2', 'country_iso_code_3', 'version');
  1009. if (!is_array($match)) {
  1010. $match = array();
  1011. }
  1012. if (!in_array($sort, $valid_fields)) {
  1013. $sort = 'country_name';
  1014. }
  1015. $query = db_select('uc_countries')
  1016. ->fields('uc_countries')
  1017. ->orderBy($sort);
  1018. if (count($match) > 0) {
  1019. $where = '';
  1020. foreach ($match as $key => $value) {
  1021. if (!in_array($key, $valid_fields)) {
  1022. continue;
  1023. }
  1024. $query->condition($key, $value);
  1025. }
  1026. }
  1027. $countries = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
  1028. return empty($countries) ? FALSE : $countries;
  1029. }
  1030. /**
  1031. * Returns the name of an address field.
  1032. */
  1033. function uc_get_field_name($field) {
  1034. $fields = array(
  1035. 'first_name' => t('First name'),
  1036. 'last_name' => t('Last name'),
  1037. 'company' => t('Company'),
  1038. 'street1' => t('Street address'),
  1039. 'street2' => t(''),
  1040. 'city' => t('City'),
  1041. 'zone' => t('State/Province'),
  1042. 'country' => t('Country'),
  1043. 'postal_code' => t('Postal code'),
  1044. 'phone' => t('Phone number'),
  1045. 'email' => t('E-mail'),
  1046. );
  1047. if (!isset($fields[$field])) {
  1048. drupal_set_message(t('The field title %field is being accessed incorrectly.', array('%field' => $field)), 'error');
  1049. return '';
  1050. }
  1051. return variable_get('uc_field_' . $field, $fields[$field]);
  1052. }
  1053. /**
  1054. * Returns TRUE if the address field is enabled.
  1055. */
  1056. function uc_address_field_enabled($field = NULL) {
  1057. $fields = variable_get('uc_address_fields', drupal_map_assoc(array('first_name', 'last_name', 'phone', 'company', 'street1', 'street2', 'city', 'zone', 'postal_code', 'country')));
  1058. return $field ? isset($fields[$field]) : $fields;
  1059. }
  1060. /**
  1061. * Returns TRUE if the address field is required.
  1062. */
  1063. function uc_address_field_required($field) {
  1064. $fields = variable_get('uc_address_fields_required', drupal_map_assoc(array('first_name', 'last_name', 'street1', 'city', 'zone', 'postal_code', 'country')));
  1065. return isset($fields[$field]);
  1066. }
  1067. /**
  1068. * Returns the weights of address fields.
  1069. */
  1070. function uc_store_address_field_weights() {
  1071. return variable_get('uc_address_fields_weight', array('first_name' => 0, 'last_name' => 1, 'company' => 2, 'street1' => 3, 'street2' => 4, 'city' => 5, 'zone' => 6, 'country' => 7, 'postal_code' => 8, 'phone' => 9));
  1072. }
  1073. /**
  1074. * A simple Forms API textfield generator...
  1075. */
  1076. function uc_textfield($title, $default = NULL, $required = TRUE, $description = NULL, $maxlength = 32, $size = 32) {
  1077. if (is_null($title) || empty($title))
  1078. return NULL;
  1079. $textfield = array(
  1080. '#type' => 'textfield',
  1081. '#title' => $title,
  1082. '#description' => $description,
  1083. '#size' => $size,
  1084. '#maxlength' => $maxlength,
  1085. '#required' => $required,
  1086. '#default_value' => $default,
  1087. );
  1088. return $textfield;
  1089. }
  1090. /**
  1091. * Retrieves a zone's name from the database, using its ID.
  1092. *
  1093. * @param $id
  1094. * The zone's ID.
  1095. */
  1096. function uc_zone_get_by_id($id) {
  1097. return db_query("SELECT zone_name FROM {uc_zones} WHERE zone_id = :id", array(':id' => $id))->fetchField();
  1098. }
  1099. /**
  1100. * Creates a zone select box for a form.
  1101. *
  1102. * @param $title
  1103. * The label for the field.
  1104. * @param $default
  1105. * The default zone ID.
  1106. * @param $country_id
  1107. * The country ID
  1108. * @param array $options
  1109. * An associative array of additional options, with the following elements:
  1110. * - 'description': The description for the field (defaults to none).
  1111. * - 'display': The values to display, either 'name' (default) or 'code'.
  1112. * - 'required': TRUE if the field is required (defaults to FALSE).
  1113. *
  1114. * @return
  1115. * A Form API select element.
  1116. */
  1117. function uc_zone_select($title = '', $default = NULL, $country_id = NULL, $options = array()) {
  1118. $options += array(
  1119. 'description' => NULL,
  1120. 'display' => 'name',
  1121. 'required' => FALSE,
  1122. );
  1123. if (empty($country_id)) {
  1124. $country_id = uc_store_default_country();
  1125. }
  1126. $order_by = ($options['display'] == 'code') ? 'zone_code' : 'zone_name';
  1127. $result = db_query('SELECT * FROM {uc_zones} WHERE zone_country_id = :id ORDER BY :field', array(':id' => $country_id, ':field' => $order_by));
  1128. $zones = array('' => t('Please select'));
  1129. foreach ($result as $zone) {
  1130. $zones[$zone->zone_id] = $zone->$order_by;
  1131. }
  1132. if (count($zones) == 1) {
  1133. $zones = array(-1 => t('Not applicable'));
  1134. }
  1135. $select = array(
  1136. '#type' => 'select',
  1137. '#title' => $title,
  1138. '#description' => $options['description'],
  1139. '#options' => $zones,
  1140. '#default_value' => $default,
  1141. '#required' => $options['required'],
  1142. '#disabled' => isset($zones[-1]),
  1143. );
  1144. return $select;
  1145. }
  1146. /**
  1147. * Helper function to return zone options, grouped by country.
  1148. */
  1149. function uc_zone_option_list() {
  1150. $result = db_query("SELECT z.*, c.country_name FROM {uc_zones} z LEFT JOIN {uc_countries} c ON z.zone_country_id = c.country_id ORDER BY c.country_name, z.zone_name");
  1151. foreach ($result as $zone) {
  1152. $options[t($zone->country_name)][$zone->zone_id] = $zone->zone_name;
  1153. }
  1154. uksort($options, 'strnatcasecmp');
  1155. return $options;
  1156. }
  1157. /**
  1158. * Retrieves a country's name from the database, using its ID.
  1159. *
  1160. * @param $id
  1161. * The country's ISO 3166-1 numeric identifier.
  1162. */
  1163. function uc_country_get_by_id($id) {
  1164. return db_query("SELECT country_name FROM {uc_countries} WHERE country_id = :id", array(':id' => $id))->fetchField();
  1165. }
  1166. /**
  1167. * Returns a list of available countries.
  1168. */
  1169. function uc_country_option_list() {
  1170. $result = db_query("SELECT * FROM {uc_countries} WHERE version > :version", array(':version' => 0));
  1171. $options = array();
  1172. while ($country = $result->fetchAssoc()) {
  1173. $options[$country['country_id']] = t($country['country_name']);
  1174. }
  1175. if (count($options) == 0) {
  1176. $options[] = t('No countries found.');
  1177. }
  1178. natcasesort($options);
  1179. return $options;
  1180. }
  1181. /**
  1182. * Creates a day select box for a form.
  1183. */
  1184. function uc_select_day($title = NULL, $default = NULL, $allow_empty = FALSE) {
  1185. $options = $allow_empty ? array('' => '') : array();
  1186. $select = array(
  1187. '#type' => 'select',
  1188. '#title' => (is_null($title) ? t('Day') : $title),
  1189. '#options' => $options + drupal_map_assoc(range(1, 31)),
  1190. '#default_value' => (is_null($default) ? 0 : $default),
  1191. );
  1192. return $select;
  1193. }
  1194. /**
  1195. * Creates a month select box for a form.
  1196. */
  1197. function uc_select_month($title = NULL, $default = NULL, $allow_empty = FALSE) {
  1198. $options = $allow_empty ? array('' => '') : array();
  1199. $select = array(
  1200. '#type' => 'select',
  1201. '#title' => (is_null($title) ? t('Month') : $title),
  1202. '#options' => $options +
  1203. array(1 => t('01 - January'), 2 => t('02 - February'),
  1204. 3 => t('03 - March'), 4 => t('04 - April'),
  1205. 5 => t('05 - May'), 6 => t('06 - June'),
  1206. 7 => t('07 - July'), 8 => t('08 - August'),
  1207. 9 => t('09 - September'), 10 => t('10 - October'),
  1208. 11 => t('11 - November'), 12 => t('12 - December')
  1209. ),
  1210. '#default_value' => (is_null($default) ? 0 : $default),
  1211. );
  1212. return $select;
  1213. }
  1214. /**
  1215. * Creates a year select box for a form.
  1216. */
  1217. function uc_select_year($title = NULL, $default = NULL, $min = NULL, $max = NULL, $allow_empty = FALSE) {
  1218. $min = is_null($min) ? intval(date('Y')) : $min;
  1219. $max = is_null($max) ? intval(date('Y')) + 20 : $max;
  1220. $options = $allow_empty ? array('' => '') : array();
  1221. $select = array(
  1222. '#type' => 'select',
  1223. '#title' => (is_null($title) ? t('Year') : $title),
  1224. '#options' => $options + drupal_map_assoc(range($min, $max)),
  1225. '#default_value' => (is_null($default) ? 0 : $default),
  1226. );
  1227. return $select;
  1228. }
  1229. /**
  1230. * Creates an address select box based on a user's previous orders.
  1231. *
  1232. * @param $uid
  1233. * The user's ID to search for in the orders table.
  1234. * @param $type
  1235. * Choose either 'shipping' or 'billing'.
  1236. */
  1237. function uc_select_address($uid, $type = 'billing', $onchange = '', $title = NULL) {
  1238. $addresses = uc_get_addresses($uid, $type);
  1239. if (!is_array($addresses) || count($addresses) == 0) {
  1240. return NULL;
  1241. }
  1242. $options = array('0' => t('Select one...'));
  1243. foreach ($addresses as $key => $address) {
  1244. $option = $address['street1'];
  1245. // Check if the address is a duplicate (i.e. same address, but sent to
  1246. // different person).
  1247. if ((isset($addresses[$key - 1]) && $option == $addresses[$key - 1]['street1']) ||
  1248. (isset($addresses[$key + 1]) && $option == $addresses[$key + 1]['street1'])) {
  1249. $option .= ' - ' . $address['first_name'] . ' ' . $address['last_name'];
  1250. }
  1251. $options[drupal_json_encode($address)] = check_plain($option);
  1252. }
  1253. $select = array(
  1254. '#type' => 'select',
  1255. '#title' => is_null($title) ? t('Address book') : $title,
  1256. '#options' => $options,
  1257. '#attributes' => array('onchange' => $onchange),
  1258. );
  1259. return $select;
  1260. }
  1261. /**
  1262. * Creates an address select box based on a user's previous orders.
  1263. *
  1264. * @param $uid
  1265. * The user's ID to search for in the orders table.
  1266. * @param $type
  1267. * Choose either 'shipping' or 'billing'.
  1268. */
  1269. function uc_select_addresses($uid, $type = 'billing') {
  1270. $addresses = uc_get_addresses($uid, $type);
  1271. if (empty($addresses)) {
  1272. return array();
  1273. }
  1274. $options = array(-1 => t('Select one...'));
  1275. foreach ($addresses as $key => $address) {
  1276. $option = $address['street1'];
  1277. // Check if the address is a duplicate (i.e. same address, but sent to
  1278. // different person).
  1279. if ((isset($addresses[$key - 1]) && $option == $addresses[$key - 1]['street1']) ||
  1280. (isset($addresses[$key + 1]) && $option == $addresses[$key + 1]['street1'])) {
  1281. $option .= ' - ' . $address['first_name'] . ' ' . $address['last_name'];
  1282. }
  1283. $options[$key] = check_plain($option);
  1284. }
  1285. $addresses['#options'] = $options;
  1286. return $addresses;
  1287. }
  1288. /**
  1289. * Loads a customer's previously given addresses.
  1290. */
  1291. function uc_get_addresses($uid, $type = 'billing') {
  1292. if ($uid == 0) {
  1293. return NULL;
  1294. }
  1295. if ($type == 'delivery') {
  1296. $type = 'delivery';
  1297. }
  1298. else {
  1299. $type = 'billing';
  1300. }
  1301. $query = db_select('uc_orders', 'o')->distinct();
  1302. $alias = array();
  1303. $alias['first_name'] = $query->addField('o', $type . '_first_name', 'first_name');
  1304. $alias['last_name'] = $query->addField('o', $type . '_last_name', 'last_name');
  1305. $alias['phone'] = $query->addField('o', $type . '_phone', 'phone');
  1306. $alias['company'] = $query->addField('o', $type . '_company', 'company');
  1307. $alias['street1'] = $query->addField('o', $type . '_street1', 'street1');
  1308. $alias['street2'] = $query->addField('o', $type . '_street2', 'street2');
  1309. $alias['city'] = $query->addField('o', $type . '_city', 'city');
  1310. $alias['zone'] = $query->addField('o', $type . '_zone', 'zone');
  1311. $alias['postal_code'] = $query->addField('o', $type . '_postal_code', 'postal_code');
  1312. $alias['country'] = $query->addField('o', $type . '_country', 'country');
  1313. // In pgsql, ORDER BY requires the field being sorted by to be in the SELECT
  1314. // list. But if we have the 'created' column in the SELECT list, the DISTINCT
  1315. // is rather useless. So we will just sort addresses alphabetically.
  1316. $query->condition('uid', $uid)
  1317. ->condition('order_status', uc_order_status_list('general', TRUE), 'IN')
  1318. ->orderBy($alias['street1']);
  1319. $result = $query->execute();
  1320. $addresses = array();
  1321. while ($address = $result->fetchAssoc()) {
  1322. if (!empty($address['street1']) || !empty($address['postal_code'])) {
  1323. $addresses[] = $address;
  1324. }
  1325. }
  1326. return $addresses;
  1327. }
  1328. /**
  1329. * Returns an array of country files that can be installed or updated.
  1330. */
  1331. function _uc_country_import_list() {
  1332. $dir = drupal_get_path('module', 'uc_store') . '/countries/';
  1333. $countries = array();
  1334. if (is_dir($dir)) {
  1335. if ($dh = opendir($dir)) {
  1336. while (($file = readdir($dh)) !== FALSE) {
  1337. switch (filetype($dir . $file)) {
  1338. case 'file':
  1339. if (substr($file, -4, 4) == '.cif') {
  1340. $pieces = explode('_', substr($file, 0, strlen($file) - 4));
  1341. $country_id = intval($pieces[count($pieces) - 2]);
  1342. $version = $pieces[count($pieces) - 1];
  1343. if (!isset($countries[$country_id])) {
  1344. $countries[$country_id]['version'] = $version;
  1345. $countries[$country_id]['file'] = $file;
  1346. }
  1347. else {
  1348. if ($version > $countries[$country_id]['version']) {
  1349. $countries[$country_id]['version'] = $version;
  1350. $countries[$country_id]['file'] = $file;
  1351. }
  1352. }
  1353. }
  1354. break;
  1355. }
  1356. }
  1357. closedir($dh);
  1358. }
  1359. }
  1360. return $countries;
  1361. }
  1362. /**
  1363. * Sorts an array of arrays having a weight key to determine their order.
  1364. */
  1365. function uc_weight_sort($a, $b) {
  1366. if ($a['weight'] == $b['weight']) {
  1367. return 0;
  1368. }
  1369. return ($a['weight'] > $b['weight']) ? 1 : -1;
  1370. }
  1371. /**
  1372. * Returns the default message for a configurable message.
  1373. */
  1374. function uc_get_message($message_id) {
  1375. static $messages;
  1376. if (empty($messages)) {
  1377. $messages = module_invoke_all('uc_message');
  1378. drupal_alter('uc_get_message', $messages);
  1379. }
  1380. return $messages[$message_id];
  1381. }
  1382. /**
  1383. * Returns the store name if set, or the site name otherwise.
  1384. */
  1385. function uc_store_name() {
  1386. return variable_get('uc_store_name', variable_get('site_name', 'Ubercart'));
  1387. }
  1388. /**
  1389. * Returns the user-defined store address.
  1390. */
  1391. function uc_store_address() {
  1392. $store_address = uc_address_format(NULL, NULL, uc_store_name(),
  1393. variable_get('uc_store_street1', NULL), variable_get('uc_store_street2', NULL),
  1394. variable_get('uc_store_city', NULL), variable_get('uc_store_zone', NULL),
  1395. variable_get('uc_store_postal_code', NULL), uc_store_default_country());
  1396. return $store_address;
  1397. }
  1398. /**
  1399. * Returns the store e-mail address if set, otherwise the site email address.
  1400. */
  1401. function uc_store_email() {
  1402. $email_from = variable_get('uc_store_email', '');
  1403. if (empty($email_from)) {
  1404. $email_from = variable_get('site_mail', ini_get('sendmail_from'));
  1405. }
  1406. return $email_from;
  1407. }
  1408. /**
  1409. * Returns store name and e-mail address in an RFC 2822 compliant string.
  1410. *
  1411. * The return string is intended for use as a "From" address when sending
  1412. * e-mail to customers. It will look something like:
  1413. * Store Name <store@example.com>
  1414. *
  1415. * @return
  1416. * An RFC 2822 compliant e-mail address.
  1417. */
  1418. function uc_store_email_from() {
  1419. $email_from = uc_store_email();
  1420. // Add the store name to the e-mail "From" line.
  1421. // Must be optional to prevent server conflicts.
  1422. if (variable_get('uc_store_email_include_name', TRUE)) {
  1423. $email_from = uc_store_rfc2822_display_name(uc_store_name()) . ' <' . $email_from . '>';
  1424. }
  1425. return $email_from;
  1426. }
  1427. /**
  1428. * Turns a text string into a valid RFC 2822 quoted string.
  1429. *
  1430. * Any text string not consisting of a limited set of valid characters
  1431. * (notable printable non-valid characters include ',' and '.') needs
  1432. * to be quoted in order to be used an an e-mail header such as the "From"
  1433. * address. Double quotes in the original string are escaped (and nothing else).
  1434. *
  1435. * @param $name
  1436. * The text string to convert to a RFC 2822 quoted string.
  1437. */
  1438. function uc_store_rfc2822_display_name($name) {
  1439. // Base64 encode $name string if it contains non-ASCII characters.
  1440. $name = mime_header_encode($name);
  1441. // From RFC2822, section 3.4.2, define valid characters for an atom.
  1442. $valid_chars = "[a-zA-Z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]";
  1443. // Display name is composed of 0 or more atoms separated by white space.
  1444. if (!preg_match("/^(${valid_chars}*[ \t]*)*$/", $name)) {
  1445. return '"' . addcslashes($name, '"') . '"';
  1446. }
  1447. return $name;
  1448. }
  1449. /**
  1450. * Derives a valid username from an e-mail address.
  1451. *
  1452. * @param $email
  1453. * An e-mail address.
  1454. *
  1455. * @return
  1456. * A username derived from the e-mail address, using the part of the address
  1457. * up to the @ with integers appended to the end if needed to avoid a
  1458. * duplicate username.
  1459. */
  1460. function uc_store_email_to_username($email) {
  1461. // Default to the first part of the e-mail address.
  1462. $name = substr($email, 0, strpos($email, '@'));
  1463. // Remove possible illegal characters.
  1464. $name = preg_replace('/[^A-Za-z0-9_.-]/', '', $name);
  1465. // Trim that value for spaces and length.
  1466. $name = trim(substr($name, 0, USERNAME_MAX_LENGTH - 4));
  1467. // Make sure we don't hand out a duplicate username.
  1468. while (db_query("SELECT COUNT(uid) FROM {users} WHERE name LIKE :name", array(':name' => $name))->fetchField() > 0) {
  1469. // If the username got too long, trim it back down.
  1470. if (strlen($name) == USERNAME_MAX_LENGTH) {
  1471. $name = substr($name, 0, USERNAME_MAX_LENGTH - 4);
  1472. }
  1473. // Append a random integer to the name.
  1474. $name .= rand(0, 9);
  1475. }
  1476. return $name;
  1477. }
  1478. /**
  1479. * Logs encryption errors to watchdog.
  1480. *
  1481. * @param $crypt
  1482. * The object used to perform your encryption/decryption.
  1483. * @param $module
  1484. * The module name to specify in the watchdog notices.
  1485. */
  1486. function uc_store_encryption_errors(&$crypt, $module) {
  1487. $errors = $crypt->getErrors();
  1488. if (!empty($errors)) {
  1489. foreach ($errors as $message) {
  1490. $items[] = $message;
  1491. }
  1492. watchdog('encryption', 'Encryption failed. !messages', array('!messages' => theme('item_list', array('items' => $items))), WATCHDOG_ERROR);
  1493. }
  1494. }
  1495. /**
  1496. * Returns a default store country value.
  1497. */
  1498. function uc_store_default_country() {
  1499. static $default;
  1500. if (!empty($default)) {
  1501. return $default;
  1502. }
  1503. $default = variable_get('uc_store_country', 840);
  1504. $result = db_query("SELECT COUNT(*) FROM {uc_countries} WHERE country_id = :id AND version > :version", array(':id' => $default, ':version' => 0))->fetchField();
  1505. if ($result == 0) {
  1506. $default = db_query_range("SELECT country_id FROM {uc_countries} WHERE version > :version", 0, 1, array(':version' => 0))->fetchField();
  1507. }
  1508. return $default;
  1509. }
  1510. /**
  1511. * Gets image widgets defined by various modules.
  1512. */
  1513. function uc_store_get_image_widgets() {
  1514. return module_invoke_all('uc_image_widget');
  1515. }
  1516. /**
  1517. * Implements hook_uc_image_widget().
  1518. *
  1519. * Built-in support for Colorbox, Thickbox and Lightbox2.
  1520. */
  1521. function uc_store_uc_image_widget() {
  1522. $widgets = array();
  1523. if (module_exists('colorbox')) {
  1524. $widgets['colorbox'] = array(
  1525. 'name' => t('Colorbox'),
  1526. 'callback' => 'uc_store_image_widget_colorbox',
  1527. );
  1528. }
  1529. if (module_exists('thickbox')) {
  1530. $widgets['thickbox'] = array(
  1531. 'name' => t('Thickbox'),
  1532. 'callback' => 'uc_store_image_widget_thickbox',
  1533. );
  1534. }
  1535. if (module_exists('lightbox2')) {
  1536. $widgets['lightbox2'] = array(
  1537. 'name' => t('Lightbox2'),
  1538. 'callback' => 'uc_store_image_widget_lightbox2',
  1539. );
  1540. }
  1541. return $widgets;
  1542. }
  1543. /**
  1544. * Generates the Colorbox-specific HTML attributes.
  1545. */
  1546. function uc_store_image_widget_colorbox($rel_count) {
  1547. if (!is_null($rel_count)) {
  1548. $img_index = 'uc_image_' . $rel_count;
  1549. }
  1550. else {
  1551. $img_index = 'uc_image';
  1552. }
  1553. return ' class="colorbox" rel="' . $img_index . '"';
  1554. }
  1555. /**
  1556. * Generates the Thickbox-specific HTML attributes.
  1557. */
  1558. function uc_store_image_widget_thickbox($rel_count) {
  1559. if (!is_null($rel_count)) {
  1560. $img_index = 'uc_image_' . $rel_count;
  1561. }
  1562. else {
  1563. $img_index = 'uc_image';
  1564. }
  1565. return ' class="thickbox" rel="' . $img_index . '"';
  1566. }
  1567. /**
  1568. * Generates the Lightbox2-specific HTML attributes.
  1569. */
  1570. function uc_store_image_widget_lightbox2($rel_count) {
  1571. if (!is_null($rel_count)) {
  1572. $img_index = 'lightbox[' . $rel_count . ']';
  1573. }
  1574. else {
  1575. $img_index = 'lightbox';
  1576. }
  1577. return ' rel="' . $img_index . '"';
  1578. }
  1579. /**
  1580. * Gets the preferred language for a user's email address.
  1581. *
  1582. * @param $address
  1583. * The email address to check.
  1584. *
  1585. * @return
  1586. * The language object to be used in translation, localization, etc. If a
  1587. * user account can not be found for $address, language_default() is
  1588. * returned.
  1589. *
  1590. * @see user_preferred_language()
  1591. * @see language_default()
  1592. */
  1593. function uc_store_mail_recipient_language($address) {
  1594. // See if any user exists for this address.
  1595. $account = user_load_by_mail(trim($address));
  1596. if ($account) {
  1597. $lang_object = user_preferred_language($account);
  1598. }
  1599. // If not, site-wide default.
  1600. else {
  1601. $lang_object = language_default();
  1602. }
  1603. return $lang_object;
  1604. }
  1605. /**
  1606. * Displays prices in forms with a minimum number of decimal places.
  1607. *
  1608. * @param $price
  1609. * The price to display as the #default_value in a form field.
  1610. */
  1611. function uc_store_format_price_field_value($price) {
  1612. $exact = rtrim(number_format($price, 6, '.', ''), '0');
  1613. $round = number_format($price, variable_get('uc_currency_prec', 2), '.', '');
  1614. if ($exact == rtrim($round, '0')) {
  1615. return $round;
  1616. }
  1617. else {
  1618. return $exact;
  1619. }
  1620. }
  1621. /**
  1622. * Executes hook_uc_form_alter() implementations.
  1623. *
  1624. * API function to invoke hook_uc_form_alter() implementations allowing those
  1625. * modules to alter the form before the Drupal layer hook_form_alter() is
  1626. * invoked.
  1627. *
  1628. * @see hook_uc_form_alter()
  1629. */
  1630. function uc_form_alter(&$form, &$form_state, $form_id) {
  1631. drupal_alter('uc_form', $form, $form_state, $form_id);
  1632. }