uc_store.module 53 KB

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