12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306 |
- <?php
- /**
- * @file
- * Provides discount codes and gift certificates for Ubercart.
- *
- * Version: 2.x
- * Drupal Core: 7.x
- * Ubercart Core: 3.x
- *
- * Original code by Blake Lucchesi (www.boldsource.com)
- *
- * Maintained by
- * Chris Oden (wodenx@gmail.com)
- * David Long (dave@longwaveconsulting.com)
- *
- * Please submit issues, questions or feedback to the issue queue at
- * http://drupal.org/project/uc_coupon
- */
- /**
- * Implements hook_menu().
- */
- function uc_coupon_menu() {
- $items = array();
- $items['admin/store/coupons'] = array(
- 'title' => 'Coupons',
- 'description' => 'Manage store discount coupons.',
- 'page callback' => 'uc_coupon_display',
- 'page arguments' => array('active'),
- 'access arguments' => array('view store coupons'),
- 'type' => MENU_NORMAL_ITEM,
- 'file' => 'uc_coupon.admin.inc',
- );
- $items['admin/store/coupons/list'] = array(
- 'title' => 'Active coupons',
- 'description' => 'View active coupons.',
- 'page callback' => 'uc_coupon_display',
- 'page arguments' => array('active'),
- 'access arguments' => array('view store coupons'),
- 'type' => MENU_NORMAL_ITEM,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 0,
- );
- $items['admin/store/coupons/inactive'] = array(
- 'title' => 'Inactive coupons',
- 'description' => 'View inactive coupons.',
- 'page callback' => 'uc_coupon_display',
- 'page arguments' => array('inactive'),
- 'access arguments' => array('view store coupons'),
- 'type' => MENU_NORMAL_ITEM,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 1,
- );
- $items['admin/store/coupons/add'] = array(
- 'title' => 'Add new coupon',
- 'description' => 'Add a new coupon.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('uc_coupon_add_form'),
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_NORMAL_ITEM,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 2,
- );
- $items['admin/store/coupons/%uc_coupon'] = array(
- 'title callback' => 'uc_coupon_title',
- 'title arguments' => array(3),
- 'description' => 'View coupon details.',
- 'page callback' => 'uc_coupon_view',
- 'page arguments' => array(3),
- 'access arguments' => array('view store coupons'),
- 'type' => MENU_CALLBACK,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 3,
- );
- $items['admin/store/coupons/%uc_coupon/view'] = array(
- 'title' => 'View',
- 'description' => 'View coupon details.',
- 'access arguments' => array('view store coupons'),
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 0,
- );
- $items['admin/store/coupons/%uc_coupon/print'] = array(
- 'title' => 'Print',
- 'description' => 'Print coupon.',
- 'page callback' => 'uc_coupon_print',
- 'page arguments' => array(3, 5, 'print'),
- 'access arguments' => array('view store coupons'),
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 1,
- );
- $items['admin/store/coupons/%uc_coupon/edit'] = array(
- 'title' => 'Edit',
- 'description' => 'Edit an existing coupon.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('uc_coupon_add_form', 3),
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 2,
- );
- $items['admin/store/coupons/%uc_coupon/delete'] = array(
- 'title' => 'Delete',
- 'description' => 'Delete a coupon.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('uc_coupon_delete_confirm', 3),
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'uc_coupon.admin.inc',
- 'weight' => 3,
- );
- $items['admin/store/coupons/%uc_coupon/codes'] = array(
- 'title' => 'Download bulk coupon codes',
- 'description' => 'Download the list of bulk coupon codes as a CSV file.',
- 'page callback' => 'uc_coupon_codes_csv',
- 'page arguments' => array(3),
- 'access arguments' => array('view store coupons'),
- 'file' => 'uc_coupon.admin.inc',
- 'type' => MENU_CALLBACK,
- );
- $items['admin/store/coupons/autocomplete/node'] = array(
- 'title' => 'Node autocomplete',
- 'page callback' => 'uc_coupon_autocomplete_node',
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_CALLBACK,
- 'file' => 'uc_coupon.admin.inc',
- );
- $items['admin/store/coupons/autocomplete/term'] = array(
- 'title' => 'Term autocomplete',
- 'page callback' => 'uc_coupon_autocomplete_term',
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_CALLBACK,
- 'file' => 'uc_coupon.admin.inc',
- );
- $items['admin/store/coupons/autocomplete/user'] = array(
- 'title' => 'User autocomplete',
- 'page callback' => 'uc_coupon_autocomplete_user',
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_CALLBACK,
- 'file' => 'uc_coupon.admin.inc',
- );
- $items['admin/store/coupons/autocomplete/role'] = array(
- 'title' => 'Role autocomplete',
- 'page callback' => 'uc_coupon_autocomplete_role',
- 'access arguments' => array('manage store coupons'),
- 'type' => MENU_CALLBACK,
- 'file' => 'uc_coupon.admin.inc',
- );
- $items['admin/store/settings/coupon'] = array(
- 'title' => 'Coupon module settings',
- 'description' => 'Configure the discount coupon module settings.',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('uc_coupon_settings_form'),
- 'access arguments' => array('administer store'),
- 'file' => 'uc_coupon.admin.inc',
- 'type' => MENU_NORMAL_ITEM,
- );
- $items['admin/store/settings/coupon/settings'] = array(
- 'title' => 'Settings',
- 'description' => 'Edit the basic coupon settings.',
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- 'weight' => -10,
- );
- $items['admin/store/reports/coupon'] = array(
- 'title' => 'Coupon usage reports',
- 'description' => 'View coupon usage reports.',
- 'page callback' => 'uc_coupon_reports',
- 'access arguments' => array('view reports'),
- 'file' => 'uc_coupon.reports.inc',
- 'type' => MENU_NORMAL_ITEM,
- );
- return $items;
- }
- /**
- * Properly handle %uc_coupon wildcard.
- * (Necessary to prevent PHP runtime notice.)
- */
- function uc_coupon_to_arg($arg) {
- return $arg;
- }
- /**
- * Title callback for coupon print preview.
- */
- function uc_coupon_title($coupon) {
- return $coupon->name;
- }
- /**
- * Implements hook_permission().
- */
- function uc_coupon_permission() {
- $perms = array(
- 'view store coupons' => array(
- 'title' => t('view store coupons'),
- 'description' => t('Display information about discount coupons.'),
- ),
- 'manage store coupons' => array(
- 'title' => t('manage store coupons'),
- 'description' => t('Create, edit and delete discoutn coupons.'),
- ),
- );
- if (!module_exists('uc_reports')) {
- $perms['view reports'] = array(
- 'title' => t('view reports'),
- 'description' => t('Display coupon usage reports.')
- );
- }
- return $perms;
- }
- /**
- * Implements hook_init().
- */
- function uc_coupon_init() {
- global $conf;
- $conf['i18n_variables'][] = 'uc_coupon_pane_description';
- // Auto apply coupon from query string, if configured.
- if ($param = variable_get('uc_coupon_querystring', '')) {
- if (isset($_GET[$param]) && $_GET[$param]) {
- // We retain the querystring coupon so that it will validate if/when appropriate conditions are met.
- uc_coupon_session_add($_GET[$param], 'retain');
- }
- }
- }
- /**
- * Implements hook_theme().
- */
- function uc_coupon_theme() {
- return array(
- 'uc_coupon_automatic_discounts' => array(
- 'render element' => 'form',
- ),
- 'uc_coupon_form' => array(
- 'render element' => 'form',
- ),
- 'uc_coupon_actions' => array(
- 'variables' => array('coupon' => NULL),
- 'file' => 'uc_coupon.admin.inc',
- ),
- 'uc_coupon_code' => array(
- 'variables' => array('coupon' => NULL),
- 'file' => 'uc_coupon.admin.inc',
- ),
- 'uc_coupon_discount' => array(
- 'variables' => array('coupon' => NULL, 'currency' => TRUE),
- ),
- 'uc_coupon_certificate' => array(
- 'variables' => array('coupon' => NULL, 'code' => NULL),
- 'template' => 'uc-coupon-certificate',
- 'path' => drupal_get_path('module', 'uc_coupon') . '/theme',
- ),
- 'uc_coupon_page' => array(
- 'variables' => array('content' => NULL),
- 'template' => 'uc-coupon-page',
- 'path' => drupal_get_path('module', 'uc_coupon') . '/theme',
- ),
- );
- }
- /**
- * Implements hook_theme_registry_alter().
- */
- function uc_coupon_theme_registry_alter(&$registry) {
- // Override the default theme for the cart block content - but only if not already overridden.
- if ($registry['uc_cart_block_content']['function'] == 'theme_uc_cart_block_content') {
- $registry['uc_cart_block_content']['function'] = 'uc_coupon_theme_uc_cart_block_content';
- }
- }
- /**
- * Count usage of a coupon.
- *
- * @param $cid
- * The coupon id to count.
- * @param $uid
- * (optional) The user id to count. Defaults to the current user.
- * @param array $exclude_oids
- * (optional) If supplied, will exclude usage for the specified order ids.
- *
- * @return
- * An associative array containing:
- * - codes: An associative array of code => usage count.
- * - user: The usage count by the specified (or current) user.
- */
- function uc_coupon_count_usage($cid, $uid = NULL, $exclude_oids = array()) {
- global $user;
- $weight = uc_order_status_data(variable_get('uc_coupon_used_order_status', 'processing'), 'weight');
- $usage = array('codes' => array(), 'value' => array('codes' => array()));
- $exclude_where = empty($exclude_oids) ? '' : 'AND uo.order_id NOT IN (:oids)';
- $result = db_query("SELECT uco.code, COUNT(*) AS uses, SUM(uco.value) AS value FROM {uc_coupons_orders} AS uco
- LEFT JOIN {uc_orders} AS uo ON uco.oid = uo.order_id
- LEFT JOIN {uc_order_statuses} AS uos ON uo.order_status = uos.order_status_id
- WHERE uos.weight >= :weight AND uco.cid = :cid $exclude_where GROUP BY uco.code",
- array( ':weight' => $weight, ':cid' => $cid, ':oids' => $exclude_oids));
- foreach ($result as $row) {
- $usage['codes'][$row->code] = $row->uses;
- $usage['value']['codes'][$row->code] = $row->value;
- }
- if (is_null($uid)) {
- $uid = $user->uid;
- }
- $usage['user'] = db_query("SELECT COUNT(*) FROM {uc_coupons_orders} AS uco
- LEFT JOIN {uc_orders} AS uo ON uco.oid = uo.order_id
- LEFT JOIN {uc_order_statuses} AS uos ON uo.order_status = uos.order_status_id
- WHERE uos.weight >= :weight AND uco.cid = :cid AND uo.uid = :uid",
- array( ':weight' => $weight, ':cid' => $cid, ':uid' => $uid))->fetchField();
- // Allow other modules to implement usage counts.
- drupal_alter('uc_coupon_usage', $usage, $cid, $uid);
- return $usage;
- }
- /**
- * Theme for a coupon discount.
- * @param $variables
- * 'coupon' => The coupon whose discount is to be themed.
- * 'currency' => TRUE to include currency symbols.
- */
- function theme_uc_coupon_discount($variables) {
- $coupon = $variables['coupon'];
- $currency = isset($variables['currency']) ? $variables['currency'] : TRUE;
- return _uc_coupon_format_discount($coupon, $currency);
- }
- /**
- * Format a coupon's value depending on the type, optionally including currency symbols.
- */
- function _uc_coupon_format_discount($coupon, $currency = TRUE) {
- switch ($coupon->type) {
- case 'price':
- case 'credit':
- return $currency ? uc_currency_format($coupon->value) : $coupon->value;
- case 'percentage':
- return (float) $coupon->value . '%';
- case 'set_price':
- return '=' . ($currency ? uc_currency_format($coupon->value) : $coupon->value);
- }
- }
- /**
- * Generate a single bulk coupon code.
- */
- function uc_coupon_get_bulk_code($coupon, $id) {
- // If this coupon has been validated, then $coupon->code is already a bulk code.
- if (isset($coupon->valid)) {
- $prefix = drupal_substr($coupon->code, 0, strlen($coupon->code) - $coupon->data['bulk_length']);
- }
- else {
- $prefix = $coupon->code;
- }
- $id = str_pad(dechex($id), strlen(dechex($coupon->data['bulk_number'])), '0', STR_PAD_LEFT);
- $length = strlen($prefix) + $coupon->data['bulk_length'];
- return strtoupper(substr($prefix . $id . md5($coupon->bulk_seed . $id), 0, $length));
- }
- /**
- * Load a coupon (single or bulk) from the supplied code.
- * @param $code
- * The coupon code to search for.
- * @param $reset
- * If TRUE the cache of codes for this request will be purged. Any function which modifies
- * a coupon should purge the cache.
- */
- function uc_coupon_find($code, $reset = FALSE) {
- // This is expensive and can be called many times during coupon processing, so we
- // use a simple static cache.
- static $cached = array();
- if ($reset) {
- $cached = array();
- }
- if (!$code) {
- return FALSE;
- }
- elseif (array_key_exists($code, $cached)) {
- return $cached[$code];
- }
- // Look for matching single coupon first.
- $coupon = db_query("SELECT cid FROM {uc_coupons}
- WHERE code = :code AND status = 1 AND bulk = 0 AND valid_from < :now AND (valid_until = 0 OR valid_until > :now)",
- array(':code' => $code, ':now' => REQUEST_TIME))
- ->fetchObject();
- if ($coupon) {
- $cached[$code] = uc_coupon_load($coupon->cid);
- return $cached[$code];
- }
- // Look through bulk coupons.
- $result = db_query("SELECT cid, code, data, bulk_seed FROM {uc_coupons}
- WHERE status = 1 AND bulk = 1 AND valid_from < :now AND (valid_until = 0 OR valid_until > :now)",
- array(':now' => REQUEST_TIME));
- foreach ($result as $coupon) {
- // Check coupon prefix.
- $prefix_length = strlen($coupon->code);
- if (substr($code, 0, $prefix_length) != $coupon->code) {
- continue;
- }
- if ($coupon->data) {
- $coupon->data = unserialize($coupon->data);
- }
- // Check coupon sequence ID.
- $id = substr($code, $prefix_length, strlen(dechex($coupon->data['bulk_number'])));
- if (!preg_match("/^[0-9A-F]+$/", $id)) {
- continue;
- }
- $id = hexdec($id);
- if ($id < 0 || $id > $coupon->data['bulk_number']) {
- continue;
- }
- // Check complete coupon code.
- if ($code == uc_coupon_get_bulk_code($coupon, $id)) {
- $cached[$code] = uc_coupon_load($coupon->cid);
- return $cached[$code];
- }
- }
- $cached[$code] = FALSE;
- return $cached[$code];
- }
- /**
- * Adds or updates a coupon code for the current session.
- *
- * @param $code
- * The code to add or update.
- * @param $op
- * Specifies the way the code should be handled the next time session codes are validated.
- * - 'submit' - If the code fails validation it is removed from the session; otherwise it is retained.
- * A success or failure message is displayed. This is the default operation performed when
- * a customer enters a coupon code manually.
- * - 'retain' - The code remains in the session whether or not it passes validation. A success
- * message is displayed the first time the coupon passes. This is useful for
- * codes which are added automatically in response to events occurring before any products
- * have been added to the cart (e.g. via the querystring).
- * - 'auto' - The code is removed from the session whether or not it passes validation. However, if
- * it does validate, the corresponding coupon will be considered valid for the current request only.
- * This is useful for modules implementing hook_uc_coupon_revalidate(), which can decide whether or not
- * to add their codes each time the valid coupon cache is rebuilt (e.g. automatic discounts based on
- * additional conditions).
- */
- function uc_coupon_session_add($code, $op = 'submit') {
- if (!variable_get('uc_coupon_allow_multiple', FALSE)) {
- $_SESSION['uc_coupons'] = array($code => $op);
- }
- else {
- $_SESSION['uc_coupons'][$code] = $op;
- }
- }
- /**
- * Removes one (or all) coupon codes from the session.
- *
- * @param $code
- * The code to remove, or NULL to remove all codes.
- * @param $is_update
- * TRUE (the default) if removing this code represents an update of the session; that is, if the code
- * was previously validated. FALSE otherwise (e.g. for removal of automatic discounts). Ignored if
- * a specific code is not specified.
- */
- function uc_coupon_session_clear($code = NULL) {
- if (isset($code)) {
- unset($_SESSION['uc_coupons'][$code]);
- }
- else {
- unset($_SESSION['uc_coupons']);
- }
- }
- /**
- * Checks to see if a given code is present in the session, or returns an associative array
- * of all codes in the session.
- *
- * @param $code
- * (optional) The code to chec for. If not specified, will return all codes.
- *
- * @return
- * If a code is specified, returns TRUE if that code exists in the session, FALSE otherwise. If no
- * code is specified, returns an array of the form $code=>$op for all codes in the session.
- */
- function uc_coupon_session_get($code = NULL) {
- if (isset($code)) {
- return isset($_SESSION['uc_coupons'][$code]);
- }
- elseif (isset($_SESSION['uc_coupons'])) {
- return $_SESSION['uc_coupons'];
- }
- else {
- return array();
- }
- }
- /**
- * Validates all coupons in the current session. The validated coupons are statically
- * cached for each request. The cache is rebuilt the first time this function is called,
- * or every time the cart contents are rebuilt.
- *
- * @param $order
- * An order against which to validate the currently applied codes. If specified
- * the cached list of valid coupons is rebuilt by revalidating all the codes in
- * the session against that order.
- *
- * @return
- * An array of fully validated coupon objects, indexed by code.
- */
- function uc_coupon_session_validate($order = NULL) {
- static $valids = NULL;
- // If a list of products is specified, then rebuild the list.
- if (isset($order)) {
- $valids = array();
- $order = clone $order; // We don't want to modify the order passed in.
- // Allow modules an opportunity to add or remove coupons from the session.
- module_invoke_all('uc_coupon_revalidate', $order);
- // Fetch all codes in the session.
- $session = uc_coupon_session_get();
- if (!empty($session)) {
- // Process all coupons in the session.
- global $user;
- $order->data['coupons'] = array();
- foreach ($session as $code => $op) {
- $coupon = uc_coupon_validate($code, $order, $user);
- if ($coupon->valid) { // Process valid coupons.
- $valids[$code] = $coupon;
- $order->data['coupons'][$code] = $coupon->discounts;
- switch ($op) {
- case 'submit':
- case 'retain':
- // For coupons which were not valid (new submissions) we notify user and modules.
- drupal_set_message($coupon->message);
- module_invoke_all('uc_coupon_apply', $coupon);
- // And we mark them for revalidation.
- uc_coupon_session_add($code, 'revalidate');
- break;
- case 'auto':
- // Automatic coupons are never added to the session.
- uc_coupon_session_clear($code);
- break;
- }
- }
- else { // Process invalid coupons.
- switch ($op) {
- case 'submit':
- // For new coupon submissions, just issue an error and remove from session.
- drupal_set_message($coupon->message, 'error');
- uc_coupon_session_clear($code);
- break;
- case 'revalidate':
- if (!empty($products)) { // Only issue a message if the cart is not empty.
- drupal_set_message(t('%title is no longer applicable to your order', array('%title' => $coupon->title)));
- }
- module_invoke_all('uc_coupon_remove', $coupon);
- // Keep code in the session in case it becomes valid again.
- uc_coupon_session_add($code, 'retain');
- break;
- case 'auto':
- // Automatic coupons are never added to the session.
- uc_coupon_session_clear($code);
- break;
- }
- }
- }
- }
- }
- // If no argument specified and the cache has not been built, we rebuild the cart to force validation.
- elseif (!isset($valids)) {
- uc_cart_get_contents();
- }
- return $valids;
- }
- /**
- * Validates a list of coupon codes against a specified order and account.
- *
- * @param $codes
- * The codes to be validated.
- * @param $order
- * The order that the coupon is being applied to.
- * If NULL, the current cart contents will be used.
- * If FALSE, product and order validation will be bypassed.
- * @param $account
- * The user who is attempting to use the coupon.
- * If NULL, the current user will be assumed.
- * If FALSE, user validation will be bypassed.
- *
- * @see uc_coupon_validate()
- * @see uc_coupon_session_validate()
- */
- function uc_coupon_validate_multiple($codes, $order, $account) {
- $order = clone $order; // We don't want to modify the order passed in.
- $order->data['coupons'] = array();
- $valids = array();
- $invalids = array();
- foreach ($codes as $code) {
- $coupon = uc_coupon_validate($code, $order, $account);
- if ($coupon->valid) { // Process valid coupons.
- $valids[$code] = $coupon;
- $order->data['coupons'][$code] = $coupon->discounts;
- }
- else {
- $invalids[$code] = $code;
- }
- }
- return array('valid' => $valids, 'invalid' => $invalids);
- }
- /**
- * Validate a coupon, and optionally calculate the order discount.
- *
- * @param $code
- * The coupon code entered at the checkout screen.
- * @param $order
- * The order that the coupon is being applied to.
- * If NULL, the current cart contents will be used.
- * If FALSE, product and order validation will be bypassed.
- * @param $account
- * The user who is attempting to use the coupon.
- * If NULL, the current user will be assumed.
- * If FALSE, user validation will be bypassed.
- *
- * @return
- * A coupon object with extended information about the validation:
- * - $coupon->valid: TRUE if the code was valid, FALSE otherwise.
- * - $coupon->code: The specific code to be applied (even for bulk coupons).
- * - $coupon->title: The line item title for the discount.
- * - $coupon->message: A message to be displayed accepting the acceptance or rejection of this coupon.
- * - $coupon->amount: If $order !== FALSE, the discount that should be applied.
- * - $coupon->discounts: if $order !== FALSE, an array discounts on individual products indexed by nid,
- * containing the following fields:
- * -> 'discount' = The full value of the discount on that item.
- * -> 'pretax_discount' => The actual pre-tax discount. For fixed discounts to products with
- * taxes included, we apply the face value of the coupon tax-inclusively also; that is,
- * the actual discount is calculated so that the face value is correct after taxes.
- */
- function uc_coupon_validate($code, $order = NULL, $account = NULL) {
- global $user;
- if (is_null($order)) {
- $order = new stdClass();
- $order->products = uc_cart_get_contents();
- }
- if (is_null($account)) {
- $account = $user;
- }
- // Look for an active coupon matching the code.
- $code = trim(strtoupper($code));
- $coupon = uc_coupon_find($code);
- if (!$coupon) {
- $coupon = new stdClass();
- $coupon->valid = FALSE;
- $coupon->message = t('This coupon code is invalid or has expired.');
- $coupon->title = t('Unknown');
- return $coupon;
- }
- // Count usage for this coupon.
- $uid = !empty($account) ? $account->uid : NULL;
- // If the order exists, don't count it towards coupon usage.
- $oids = !empty($order->order_id) ? array($order->order_id) : array();
- $coupon->usage = uc_coupon_count_usage($coupon->cid, $uid, $oids);
- // Calculate the discounts (if any).
- uc_coupon_prepare($coupon, $code, uc_coupon_calculate_discounts($coupon, $order));
- // Invoke validation hook.
- foreach (module_implements('uc_coupon_validate') as $module) {
- $callback = $module . '_uc_coupon_validate';
- $result = $callback($coupon, $order, $account);
- if ($result === TRUE) {
- // This module wishes the coupon to be accepted.
- $coupon->valid = TRUE;
- }
- elseif (!is_null($result)) {
- // This module wishes the coupon to be rejected.
- $coupon->valid = FALSE;
- $coupon->message = $result;
- }
- }
- // Create a success message.
- if ($coupon->valid && !isset($coupon->message)) {
- if (isset($coupon->data['apply_message'])) {
- $coupon->message = token_replace(check_plain($coupon->data['apply_message']), array('uc_coupon' => $coupon));
- }
- else {
- $amount = theme('uc_price', array('price' => $coupon->amount));
- if (isset($order) || variable_get('uc_coupon_show_in_cart', TRUE)) {
- $coupon->message = t('A discount of !amount has been applied to your order.', array('!amount' => $amount));
- }
- else {
- $coupon->message = t('A discount of !amount will be applied at checkout.', array('!amount' => $amount));
- }
- }
- }
- return $coupon;
- }
- /**
- * Prepares a coupon for validation and application to an order.
- *
- * @param $coupon
- * A raw coupon object.
- * @param $discounts
- * An associative array of the discounts to be applied, keyed by nid or -lid. Or a string
- * containing a message indicating why there are no discounts available.
- *
- * @return
- * A fully validated coupon object with all additional properties set. This is returned
- * for convenience, as the $coupon provided is passed by reference and modified directly.
- *
- * @see uc_coupon_validate().
- */
- function uc_coupon_prepare($coupon, $code, $discounts) {
- $coupon->code = $code;
- $coupon->valid = TRUE;
- $coupon->amount = 0;
- $coupon->pretax_amount = 0;
- if (!is_array($discounts)) {
- $coupon->discounts = array();
- $coupon->message = $discounts;
- }
- else {
- $coupon->discounts = $discounts;
- foreach ($coupon->discounts as $item) {
- $coupon->amount += $item->discount;
- $coupon->pretax_amount += isset($item->pretax_discount) ? $item->pretax_discount : $item->discount;
- }
- $coupon->amount = round($coupon->amount, variable_get('uc_currency_prec', 2));
- unset($coupon->message);
- }
- // Create the line item title for this coupon.
- $format = !empty($coupon->data['line_item_format']) ? $coupon->data['line_item_format'] :
- variable_get('uc_coupon_line_item_format', t('Coupon !code', array('!code' => '[uc_coupon:code]')));
- $coupon->title = token_replace(check_plain($format), array('uc_coupon' => $coupon));
- return $coupon;
- }
- /**
- * Implements hook_uc_coupon_validate().
- *
- * We implement our own hook to allow other modules a chance to run before us.
- *
- * @param $coupon
- * The coupon object to validate, with special fields set as follows:
- * - $coupon->code: The specific code to be applied (even for bulk coupons).
- * - $coupon->amount: If $order !== FALSE, the discount that should be applied.
- * - $coupon->usage: Coupon usage data from uc_coupon_count_usage().
- * @param $order
- * The order against which this coupon is to be applied, or FALSE to bypass
- * order validation.
- * @param $account
- * The account of the user trying to use the coupon, or FALSE to bypass user
- * validation.
- *
- * @return
- * TRUE if the coupon should be accepted.
- * NULL to allow other modules to determine validation.
- * Otherwise, a string describing the reason for failure.
- */
- function uc_coupon_uc_coupon_validate(&$coupon, $order, $account) {
- // Coupons which produce no discount are not valid unless they are store credit
- // type, or have no face value (e.g. free shipping).
- if ($coupon->type !== 'credit' && $coupon->value != 0 && $coupon->amount == 0) {
- $coupon->valid = FALSE;
- return !empty($coupon->message) ? $coupon->message : t('This coupon is not applicable to your order.');
- }
- // Check for allowed combinations.
- if (!empty($order->data['coupons'])) {
- foreach (array_keys($order->data['coupons']) as $code) {
- $other = uc_coupon_find($code);
- $other_listed = !empty($coupon->data['combinations']) && in_array($other->cid, $coupon->data['combinations']);
- $this_ok = (isset($coupon->data['negate_combinations']) xor $other_listed);
- $this_listed = !empty($other->data['combinations']) && in_array($coupon->cid, $other->data['combinations']);
- $other_ok = (isset($other->data['negate_combinations']) xor $this_listed);
- if (!$this_ok || !$other_ok) {
- return t('This coupon combination is not allowed.');
- }
- }
- }
- if ($coupon->type !== 'credit') {
- // Check maximum usage per code.
- if ($coupon->max_uses > 0 && !empty($coupon->usage['codes'][$coupon->code]) && $coupon->usage['codes'][$coupon->code] >= $coupon->max_uses) {
- return t('This coupon has reached the maximum redemption limit.');
- }
- // Check maximum usage per user.
- if ($account && isset($coupon->data['max_uses_per_user']) && $coupon->usage['user'] >= $coupon->data['max_uses_per_user']) {
- return t('This coupon has reached the maximum redemption limit.');
- }
- }
- else {
- if (!empty($coupon->usage['value']['codes'][$coupon->code]) && $coupon->usage['value']['codes'][$coupon->code] >= $coupon->value) {
- return t('This coupon has reached the maximum redemption limit.');
- }
- }
- // Check user ID.
- if ($account && isset($coupon->data['users'])) {
- if (in_array("$account->uid", $coupon->data['users'], TRUE) xor !isset($coupon->data['negate_users'])) {
- return t('Your user ID is not allowed to use this coupon.');
- }
- }
- // Check roles.
- if ($account && isset($coupon->data['roles'])) {
- $role_found = FALSE;
- foreach ($coupon->data['roles'] as $role) {
- if (in_array($role, $account->roles)) {
- $role_found = TRUE;
- break;
- }
- }
- if ($role_found xor !isset($coupon->data['negate_roles'])) {
- return t('You do not have the correct permission to use this coupon.');
- }
- }
- }
- /**
- * Find items that a coupon will apply to and calculate the discounts.
- *
- * @param $coupon
- * A coupon object to apply, or a coupon code as a string.
- * @param $order
- * The order object to which the coupon should be applied.
- *
- * @return
- * An array of discounts.
- */
- function uc_coupon_calculate_discounts($coupon, $order) {
- // Can only calculate discounts if an order is provided.
- if (empty($order)) {
- return array();
- }
- if (!is_object($coupon)) {
- // If argument is a code, load the corresponding coupon.
- $coupon = uc_coupon_find($coupon);
- }
- // Discover if any items match the restrictions, and which items the discount should be calculated against.
- $restricted = isset($coupon->data['products']) || isset($coupon->data['skus']) || isset($coupon->data['terms']) || isset($coupon->data['product_types']);
- $matched = 0;
- $matched_price = 0;
- $total_qty = 0;
- $total_price = 0;
- $items = array();
- foreach ($order->products as $item) {
- if (isset($item->module) && $item->module == 'uc_coupon') {
- continue;
- }
- $node = node_load($item->nid);
- $qty = $item->qty;
- if (!$restricted) {
- // Coupons with no restrictions apply to all products.
- $include = TRUE;
- }
- else {
- // Other coupons only apply to matching products.
- $include = FALSE;
- $terms = _uc_coupon_list_terms($node);
- if (isset($coupon->data['products']) && isset($item->data['kit_id'])) {
- // Items that are part of product kits must be included or excluded all together, so we pre-empt other restrictions.
- $include = (isset($coupon->data['negate_products']) xor in_array($item->data['kit_id'], $coupon->data['products']));
- }
- else if (isset($coupon->data['products']) && (isset($coupon->data['negate_products']) xor in_array($item->nid, $coupon->data['products']))) {
- $include = TRUE;
- }
- elseif (isset($coupon->data['products']) && isset($coupon->data['negate_products']) && in_array($item->nid, $coupon->data['products'])) {
- // always exclude if in list of negated products
- }
- elseif (isset($coupon->data['terms']) && (isset($coupon->data['negate_terms']) xor count(array_intersect($terms, $coupon->data['terms'])))) {
- $include = TRUE;
- }
- elseif (isset($coupon->data['terms']) && isset($coupon->data['negate_terms']) && count(array_intersect($terms, $coupon->data['terms']))) {
- // always exclude if one of the terms is in the list of negated terms
- }
- elseif (isset($coupon->data['skus']) && _uc_coupon_match_sku($item->model, $coupon->data['skus'])) {
- $include = TRUE;
- }
- elseif (isset($coupon->data['product_types']) && in_array($node->type, $coupon->data['product_types'])) {
- $include = TRUE;
- }
- }
- // A matching product was found.
- if ($include) {
- $matched += $qty;
- $matched_price += $item->price * $qty;
- }
- $total_qty += $qty;
- $total_price += $item->price * $qty;
- // Include this item. Coupons that apply to the order subtotal affect all products.
- if ($include || $coupon->data['apply_to'] == 'subtotal') {
- $clone = clone $item;
- $clone->type = $node->type;
- $items = array_pad($items, count($items) + $qty, $clone);
- }
- }
- // If no matches were found, there are no discounts to calculate.
- if ($matched == 0) {
- return t('You do not have any applicable products in your cart.');
- }
- $use_matched = (isset($coupon->data['minimum_qty_restrict']) && $coupon->data['minimum_qty_restrict'] != FALSE);
- // Make sure the minimum quantity restriction (if any) is met.
- if (isset($coupon->data['minimum_qty'])) {
- if (($use_matched ? $matched : $total_qty) < (int)$coupon->data['minimum_qty']) {
- return t('You do not have enough applicable products in your cart.');
- }
- }
- // Make sure the minimum order total restriction (if any) is met.
- if ($coupon->minimum_order > 0) {
- if (($use_matched ? $matched_price : $total_price) < $coupon->minimum_order) {
- return $use_matched ?
- t('You have not reached the minimum total of applicable products for this coupon.') :
- t('You have not reached the minimum order total for this coupon.');
- }
- }
- // Ensure that all products match, if specified.
- if (isset($coupon->data['require_match_all']) && $matched < $total_qty) {
- return t('You have non-applicable products in your cart');
- }
- // Slice off applicable products if a limit was set.
- switch ($coupon->data['apply_to']) {
- case 'cheapest':
- usort($items, '_uc_coupon_sort_products');
- $items = array_slice($items, 0, $coupon->data['apply_count']);
- break;
- case 'expensive':
- usort($items, '_uc_coupon_sort_products');
- $items = array_slice($items, -$coupon->data['apply_count']);
- break;
- }
- // Build the discounts array and get the order total.
- $total = 0;
- $discounts = array();
- $included_rates = array();
- foreach ($items as $item) {
- if (!isset($discounts[$item->nid])) { // First entry for this product.
- // Calculate the pre-tax discount proportion for this item.
- // For fixed discounts to products with taxes included, we apply the face value of the coupon
- // tax-inclusively also; that is, the actual discount is reduced so that the face value is
- // realized after taxes. (This already happens automatically for percentage based coupons).
- $included_rate = 1;
- if (module_exists('uc_taxes')) {
- foreach (uc_taxes_rate_load() as $tax) {
- if ($tax->display_include
- && is_array($tax->taxed_line_items) && in_array('coupon', $tax->taxed_line_items)
- && in_array($item->type, $tax->taxed_product_types)
- && ($tax->shippable == 0 || $item->data['shippable'] == 1)) {
- $included_rate += $tax->rate;
- }
- }
- }
- // Adjust the price for any stacked coupons.
- $prior_discount = 0;
- if (!empty($order->data['coupons'])) {
- foreach ($order->data['coupons'] as $stacked) {
- if (isset($stacked[$item->nid])) {
- $prior_discount += $stacked[$item->nid]->pretax_discount;
- }
- }
- }
- $total -= $prior_discount * $included_rate;
- $discounts[$item->nid] = (object) array(
- 'qty' => 1,
- 'price' => $item->price - $prior_discount,
- );
- $included_rates[$item->nid] = $included_rate;
- unset($item->type);
- }
- else { // An entry for this product already exists.
- // Add this item to the total for the product.
- $discounts[$item->nid]->price += $item->price;
- $discounts[$item->nid]->qty++;
- }
- $total += $item->price * $included_rate;
- }
- // Add in discounts for any included line items.
- $items = uc_order_load_line_items($order);
- if (!empty($order->line_items) && !empty($coupon->data['line_items'])) {
- foreach ($order->line_items as $line_item) {
- if (in_array($line_item['type'], $coupon->data['line_items'])) {
- // Use a negative id to distinguish this from a product discount.
- $lid = $line_item['line_item_id'];
- $lid = is_numeric($lid) ? -$lid : $lid;
- // No tax-inclusive line items in ubercart (yet).
- $included_rate = 1;
- // Adjust the price for any stacked coupons.
- $prior_discount = 0;
- if (!empty($order->data['coupons'])) {
- foreach ($order->data['coupons'] as $stacked) {
- if (isset($stacked[$lid])) {
- $prior_discount += $stacked[$lid]->pretax_discount;
- }
- }
- }
- $discounts[$lid] = (object) array(
- 'qty' => 1,
- 'price' => $line_item['amount'] - $prior_discount,
- );
- $included_rates[$lid] = $included_rate;
- $total += $discounts[$lid]->price * $included_rate;
- }
- }
- }
- // Calculate the discounts per item.
- $value = $coupon->value;
- if ($coupon->type === 'credit' && !empty($coupon->usage['value']['codes'][$coupon->code])) {
- $value -= $coupon->usage['value']['codes'][$coupon->code];
- }
- foreach ($discounts as $id => $discount) {
- $inclusive_price = $discount->price * $included_rates[$id];
- switch ($coupon->type) {
- case 'percentage':
- $discount->discount = $inclusive_price * $coupon->value / 100;
- break;
- case 'set_price':
- $discount->discount = max($inclusive_price - ($coupon->value * $discount->qty), 0);
- break;
- default:
- if ($coupon->type === 'credit' || $coupon->data['apply_to'] == 'subtotal' || $coupon->data['apply_to'] == 'products_total') {
- // Apply single discount proportionally across all matching items.
- $discount->discount = $total == 0 ? 0 : min($value * ($inclusive_price / $total), $inclusive_price);
- }
- else {
- // Apply full discount value to each matching item.
- $discount->discount = min($value * $discount->qty, $inclusive_price);
- }
- }
- $discount->pretax_discount = $discount->discount / $included_rates[$id];
- unset($discount->price);
- unset($discount->qty);
- }
- return $discounts;
- }
- function _uc_coupon_match_sku($model, $skus) {
- foreach ($skus as $match) {
- if (preg_match('/^' . str_replace('\*', '.*?', preg_quote($match, '/')) . '$/', $model)) {
- return TRUE;
- }
- }
- return FALSE;
- }
- function _uc_coupon_sort_products($a, $b) {
- if ($a->price == $b->price) {
- return 0;
- }
- return $a->price > $b->price ? 1 : -1;
- }
- /**
- * Lists all taxonomy terms contained in 'taxonomy_term_reference' fields for a given node.
- * @param $node
- * The node whose terms should be listed;
- * @return
- * An array of any taxonomy term id's.
- */
- function _uc_coupon_list_terms($node) {
- $terms = array();
- foreach(array_keys(field_info_instances('node', $node->type)) as $field_name) {
- $field_info = field_info_field($field_name);
- if ($field_info['type'] == 'taxonomy_term_reference') {
- if ($field_values = field_get_items('node', $node, $field_name)) {
- foreach ($field_values as $field_value) {
- $terms[] = $field_value['tid'];
- }
- }
- }
- }
- return $terms;
- }
- /**
- * Implements hook_block_info().
- */
- function uc_coupon_block_info() {
- $blocks = array();
- $blocks['coupon-discount'] = array(
- 'info' => t('Coupon discount form'),
- );
- return $blocks;
- }
- /**
- * Implements hook_block_view().
- */
- function uc_coupon_block_view($delta) {
- if ($delta == 'coupon-discount') {
- $block = array(
- 'subject' => t('Coupon discount'),
- 'content' => drupal_get_form('uc_coupon_form', 'block'),
- );
- return $block;
- }
- }
- /**
- * Default theme implementation for the coupon submit form.
- */
- function theme_uc_coupon_form($variables) {
- $form = $variables['form'];
- $output = '';
- if ($form['#uc_coupon_form_context'] == 'cart') {
- $output .= '<h3>' . t('Coupon discounts') . '</h3>';
- }
- elseif ($form['#uc_coupon_form_context'] == 'block') {
- if (isset($form['code'])) {
- $form['code']['#size'] = 15;
- }
- }
- $output .= drupal_render_children($form);
- return $output;
- }
- /**
- * Implements hook_uc_cart_pane().
- */
- function uc_coupon_uc_cart_pane($items) {
- drupal_add_css(drupal_get_path('module', 'uc_coupon') . '/uc_coupon.css');
- // The coupon entry cart pane.
- $body = drupal_get_form('uc_coupon_form', 'cart') + array(
- '#prefix' => '<div id="uc-cart-pane-coupon">',
- '#suffix' => '</div>'
- );
- $panes[] = array(
- 'id' => 'coupon',
- 'body' => $body,
- 'title' => t('Coupon discount'),
- 'desc' => t('Allows shoppers to use a coupon during checkout for order discounts.'),
- 'weight' => 1,
- 'enabled' => TRUE,
- );
- // The "Special Discounts" cart pane.
- $body = array();
- $discounts = _uc_coupon_options_list(uc_coupon_session_validate(), FALSE);
- if (!empty($discounts)) {
- $body = array(
- '#theme' => 'uc_coupon_automatic_discounts',
- '#prefix' => '<div id="uc-cart-pane-coupon-automatic">',
- '#title' => t('Special discounts'),
- '#suffix' => '</div>',
- 'discounts' => array(
- '#theme' => 'item_list',
- '#items' => _uc_coupon_options_list(uc_coupon_session_validate(), FALSE),
- '#title' => t('Special discounts'),
- )
- );
- }
- $panes[] = array(
- 'id' => 'coupon_auto',
- 'body' => $body,
- 'title' => t('Special Discounts'),
- 'desc' => t('Displays a list of automatic discounts.'),
- 'weight' => 1,
- 'enabled' => TRUE,
- );
- return $panes;
- }
- function theme_uc_coupon_automatic_discounts($variables) {
- $form = $variables['form'];
- /*
- $items = $form['discounts']['#items'];
- $title = isset($form['discounts']['#title']) ? $form['discounts']['#title'] : NULL;
- $rows = array();
- foreach($items as $item) {
- $rows[] = array($item);
- }
- $form['discounts'] = array(
- '#theme' => 'table',
- '#rows' => $rows,
- );
- if (isset($title)) {
- $form['discounts']['#header'] = array($title);
- }
- */
- return drupal_render_children($form);
- }
- /**
- * Create a tapir table of validated coupons with a "Remove" button for each.
- *
- * @param $coupons
- * An array of coupon options of the form code => title
- * @param $submit
- * An array of additional options to attach to the form's submit elements (e.g. #ajax, #submit)
- */
- function uc_coupon_table($coupons, $submit = FALSE) {
- $table = array(
- '#type' => 'tapir_table',
- );
- $table['#columns'] = array(
- 'title' => array(
- 'cell' => t('Active coupons'),
- 'weight' => 0,
- ),
- 'remove' => array(
- 'cell' => t('Remove'),
- 'weight' => 1,
- ),
- );
- $i = 0;
- foreach ($coupons as $code => $title) {
- $table[$i] = array(
- 'title' => array('#markup' => $title),
- 'remove' => array(
- '#type' => 'submit',
- '#value' => t('Remove'),
- '#name' => 'uc-coupon-remove-' . $code,
- ),
- );
- if ($submit) {
- // Add ajax functionality to this table.
- $table[$i]['remove'] += $submit;
- }
- $i++;
- }
- return $table;
- }
- /**
- * Helper function to create a list of coupons for form elements.
- * @param $coupons
- * An array of validated coupon objects to include in the list.
- * @param $in_session
- * TRUE to include only coupons currently added to the session.
- * FALSE to include those not in the session (e.g. automatic coupons).
- * @return
- * An associative array mapping coupon code to coupon title.
- */
- function _uc_coupon_options_list($coupons, $in_session = TRUE) {
- $options = array();
- if (!empty($coupons)) {
- foreach ($coupons as $coupon) {
- if (!$in_session xor uc_coupon_session_get($coupon->code)) {
- $options[$coupon->code] = $coupon->title;
- if ($coupon->type === 'credit') {
- $credit = empty($coupon->usage['value']['codes'][$coupon->code]) ? 0 : $coupon->usage['value']['codes'][$coupon->code];
- $credit += empty($coupon->amount) ? 0 : $coupon->amount;
- $credit = $credit > $coupon->value ? 0 : $coupon->value - $credit;
- $options[$coupon->code] .= ' (' . t('@credit credit remaining', array('@credit' => uc_currency_format($credit))) . ')';
- }
- }
- }
- }
- return $options;
- }
- function uc_coupon_checkout_submit($form, &$form_state) {
- $form_state['rebuild'] = TRUE;
- unset($form_state['checkout_valid']);
- $form_state['redirect'] = 'cart/checkout';
- }
- function uc_coupon_order_submit($form, &$form_state) {
- $form_state['rebuild'] = TRUE;
- uc_coupon_form_submit($form, $form_state);
- $coupons = uc_coupon_get_order_coupons($form_state['order']);
- uc_coupon_apply_to_order($form_state['order'], uc_coupon_get_order_coupons($form_state['order']));
- // !TODO: Shouldn't save the order here because we prevent reverting changes
- // made by other panes?
- uc_order_save($form_state['order']);
- }
- /**
- * Form builder for the uc_coupon form.
- *
- * @param $context
- * Where the form is to appear: 'cart', 'block' or 'checkout'
- * @param $submit
- * An array of additional options to attach to the form's submit elements (e.g. #ajax, #submit)
- */
- function uc_coupon_form($form, $form_state, $context = 'block', $submit = FALSE) {
- //dpm($form_state['order'], 'order build');
- $coupons = ($context == 'order') ? uc_coupon_get_order_coupons($form_state['order']) : uc_coupon_session_validate();
- //dpm($coupons, 'coupons build');
- $components = variable_get('uc_coupon_form_components',
- drupal_map_assoc(variable_get('uc_coupon_allow_multiple', FALSE) ? array('entry') : array('entry', 'list')));
- // Show the coupon code entry component
- if (!empty($components['entry'])) {
- $form['code'] = array(
- '#type' => 'textfield',
- '#size' => 25,
- '#title' => t('Coupon Code'),
- '#description' => t('Enter a coupon code and click "Apply to order" below.'),
- );
- $form['apply'] = array(
- '#type' => 'submit',
- '#value' => t('Apply to order'),
- '#name' => 'uc-coupon-apply',
- );
- if (!variable_get('uc_coupon_allow_multiple', FALSE) && count(uc_coupon_session_get()) > 0) {
- $form['code']['#description'] .= ' ' . t('Apply a blank code to remove the currently applied coupon.');
- }
- drupal_add_css('#uc-coupon-active-coupons, #uc-coupon-other-discounts { clear: left; }', array('type' => 'inline', 'group' => CSS_DEFAULT));
- if ($submit) {
- $form['apply'] += $submit;
- }
- }
- // Add active coupons components (table and/or list).
- $options = _uc_coupon_options_list($coupons, $context != 'order');
- if (!empty($options)) {
- if (!empty($components['table'])) {
- $form['coupons_table'] = tapir_get_table('uc_coupon_table', $options, $submit);
- }
- if (!empty($components['list'])) {
- $form['coupons'] = array(
- '#prefix' => '<div id="uc-coupon-active-coupons">',
- '#suffix' => '</div>',
- '#type' => 'checkboxes',
- '#title' => t('Active Coupons'),
- '#options' => $options,
- '#default_value' => array_keys($options),
- '#description' => t('These coupons have been applied to your order. To remove one, uncheck the box and click "Remove coupons" below.'),
- );
- $form['coupons']['#description'] = t('These coupons have been applied to your order. To remove one, uncheck the box next to the coupon name and click "Update order" below.');
- $form['remove'] = array(
- '#type' => 'submit',
- '#value' => t('Update order'),
- '#name' => 'uc-coupon-remove',
- );
- if ($submit) {
- $form['remove'] += $submit;
- }
- }
- }
- // Add context to help out themers.
- $form['#uc_coupon_form_context'] = $context;
- return $form;
- }
- /**
- * Implements hook_uc_order().
- */
- function uc_coupon_uc_order($op, &$order) {
- if ($op == 'presave') {
- // Apply any session coupons to the current cart order.
- if (_uc_coupon_is_checkout_order($order)) {
- $coupons = uc_coupon_session_validate($order);
- uc_coupon_apply_to_order($order, $coupons);
- }
- // Make sure any fake cart items don't get saved with the order if the checkout page is skipped
- // (e.g. Paypal Express Checkout, Google Checkout)
- foreach ($order->products as $key => $product) {
- if (isset($product->module) && $product->module == 'uc_coupon') {
- unset($order->products[$key]);
- }
- }
- }
- }
- /**
- * Apply a set of coupons to an order.
- *
- * Line items and entries in the uc_coupons_orders table will be added for each coupon. Any coupon line items
- * or entries which are not in the list of coupons will be removed. Additionally, the coupons' discount
- * arrays will be added to the order object's data array.
- *
- * @param $order
- * The order to which the coupons should be applied.
- * @param $coupons
- * An associative array of fully validated coupon objects, keyed by the coupon code.
- */
- function uc_coupon_apply_to_order($order, $coupons) {
- // Index existing line items by coupon code.
- $items = array();
- foreach ($order->line_items as $index => $line) {
- if ($line['type'] == 'coupon') {
- // For orders created before multi-coupons were enabled, the code was not saved with the line item.
- // In this case, we retreive it from the uc_coupons_orders table.
- $code = isset($line['data']['code']) ? $line['data']['code'] :
- db_query('SELECT code FROM {uc_coupons_orders} WHERE oid = :oid', array(':oid' => $order_id))->fetchField();
- $items[$code] = $index;
- }
- }
- // Index existing entries in {uc_coupons_orders} by coupon code.
- $entries = db_query('SELECT code, cuid FROM {uc_coupons_orders} WHERE oid = :oid',
- array(':oid' => $order->order_id))->fetchAllKeyed(0,1);
- // Update, insert or delete line items and entries in uc_coupons_orders.
- $insert = array();
- $order->data['coupons'] = array();
- foreach($coupons as $coupon) {
- $order->data['coupons'][$coupon->code] = $coupon->discounts;
- // Handle entries in {uc_coupons_orders}.
- if (isset($entries[$coupon->code])) {
- db_update('uc_coupons_orders')->condition('cuid', $entries[$coupon->code])
- ->fields(array('cid' => $coupon->cid, 'value' => $coupon->amount))
- ->execute();
- unset($entries[$coupon->code]);
- }
- else {
- $insert[] = array($coupon->cid, $order->order_id, $coupon->code, $coupon->value);
- }
- // Handle line items.
- if (isset($items[$coupon->code])) {
- $line =& $order->line_items[$items[$coupon->code]];
- $line['title'] = $coupon->title;
- $line['amount'] = -$coupon->pretax_amount;
- $line['data']['code'] = $coupon->code;
- uc_order_update_line_item($line['line_item_id'], $line['title'], $line['amount'], $line['data']);
- unset($items[$coupon->code]);
- }
- else {
- // Create a new line item.
- $order->line_items[] = uc_order_line_item_add($order->order_id, 'coupon',
- $coupon->title,
- -$coupon->pretax_amount,
- _uc_line_item_data('coupon', 'weight'),
- array('code' => $coupon->code)
- );
- }
- }
- // Insert new entries in {uc_coupons_orders}
- if (!empty($insert)) {
- $query = db_insert('uc_coupons_orders');
- $query->fields(array('cid', 'oid', 'code', 'value'));
- foreach ($insert as $fields) {
- $query->values($fields);
- }
- $query->execute();
- }
- // Delete orphaned entries in {uc_coupons_orders}
- if (!empty($entries)) {
- db_delete('uc_coupons_orders')->condition('cuid', $entries)->execute();
- }
- // Remove orphaned line-items.
- foreach ($items as $index) {
- uc_order_delete_line_item($order->line_items[$index]['line_item_id']);
- unset($order->line_items[$index]);
- }
- usort($order->line_items, 'uc_weight_sort');
- }
- /**
- * Implements hook_uc_checkout_pane().
- *
- * Show a pane just above the order total that allows shoppers to enter a coupon
- * for a discount.
- */
- function uc_coupon_uc_checkout_pane() {
- $panes[] = array(
- 'id' => 'coupon',
- 'callback' => 'uc_checkout_pane_coupon',
- 'title' => t('Coupon discount'),
- 'desc' => t('Allows shoppers to use a coupon during checkout for order discounts.'),
- 'weight' => 5,
- 'process' => TRUE,
- );
- $panes[] = array(
- 'id' => 'coupon_automatic',
- 'callback' => 'uc_checkout_pane_coupon_automatic',
- 'title' => t('Special Discounts'),
- 'desc' => t('Displays a list of all automatic coupon discounts.'),
- 'weight' => 5,
- 'process' => FALSE,
- );
- return $panes;
- }
- /**
- * Ajax callback for checkout form.
- */
- function uc_coupon_checkout_update($form, $form_state) {
- $commands[] = ajax_command_replace('#coupon-pane', trim(drupal_render($form['panes']['coupon'])));
- if (isset($form['panes']['coupon_automatic'])) {
- $commands[] = ajax_command_replace('#coupon_automatic-pane', trim(drupal_render($form['panes']['coupon_automatic'])));
- }
- if (isset($form['panes']['quotes'])) {
- $commands[] = ajax_command_replace('#quotes-pane', drupal_render($form['panes']['quotes']));
- }
- if (isset($form['panes']['payment'])) {
- $commands[] = ajax_command_replace('#payment-pane', trim(drupal_render($form['panes']['payment'])));
- }
- if (variable_get('uc_coupon_show_in_cart', TRUE) && isset($form['panes']['cart']['cart_review_table'])) {
- $commands[] = ajax_command_html('#cart-pane>div', drupal_render($form['panes']['cart']['cart_review_table']));
- }
- // Clear the coupon code, but only if the submission was successful.
- if (count(drupal_get_messages('error', FALSE)) == 0) {
- $commands[] = ajax_command_invoke('#coupon-pane input[type=text]', 'val', array(''));
- }
- // Make sure all checkboxes are checked.
- $commands[] = ajax_command_invoke('#coupon-pane input[type=checkbox]', 'attr', array('checked', 'true'));
- // Show any messages.
- $commands[] = ajax_command_html('#coupon-messages', theme('status_messages'));
- return array('#type' => 'ajax', '#commands' => $commands);
- }
- /**
- * A checkout pane listing any automatic discounts.
- */
- function uc_checkout_pane_coupon_automatic($op, &$order, $form = NULL, &$form_state = NULL) {
- if ($op == 'view') {
- $discounts = _uc_coupon_options_list(uc_coupon_session_validate(), FALSE);
- if (empty($discounts)) {
- $inner_contents = array(
- '#markup' => t('None.'),
- );
- }
- else {
- $inner_contents = array(
- '#theme' => 'item_list',
- '#items' => _uc_coupon_options_list(uc_coupon_session_validate(), FALSE)
- );
- }
- return array(
- 'theme' => 'uc_coupon_automatic_discounts',
- 'contents' => array(
- 'discounts' => $inner_contents,
- ),
- );
- }
- }
- /**
- * Checkout Pane callback function.
- *
- * Used to display a form in the checkout process so that customers
- * can enter discount coupons.
- */
- function uc_checkout_pane_coupon($op, &$order, $form = NULL, &$form_state = NULL) {
- switch ($op) {
- case 'prepare':
- // Remove fake cart items from the order.
- foreach ($order->products as $key => $product) {
- if (isset($product->module) && $product->module == 'uc_coupon') {
- unset($order->products[$key]);
- }
- }
- break;
- case 'view':
- // Revalidate the session coupons against the actual order.
- drupal_add_css('#coupon-messages { clear: both; }', array('type' => 'inline', 'group' => CSS_DEFAULT));
- $description = variable_get('uc_coupon_pane_description', t('Enter a coupon code for this order.'));
- $submit = array(
- '#limit_validation_errors' => array(),
- '#ajax' => array(
- 'callback' => 'uc_coupon_checkout_update',
- ),
- '#submit' => array('uc_coupon_checkout_submit'),
- );
- $contents = uc_coupon_form(array(), $form_state, 'checkout', $submit);
- $contents['message'] = array(
- '#markup' => '<div id="coupon-messages"></div>',
- '#weight' => 2,
- );
- return array(
- 'description' => $description,
- 'contents' => $contents,
- 'theme' => 'uc_coupon_form',
- );
- case 'process':
- $trigger = $form_state['triggering_element']['#name'];
- if (substr($trigger, 0, 9) == 'uc-coupon') {
- $form_state['rebuild'] = TRUE;
- uc_coupon_form_submit($form['panes']['coupon'], $form_state);
- return FALSE; // Prevent redirection.
- }
- else {
- // !TODO Coupon will not be submitted if "Apply to order" is not clicked. Is this what we want?
- return TRUE;
- }
- case 'settings':
- $form['uc_coupon_collapse_pane'] = array(
- '#type' => 'checkbox',
- '#title' => t('Collapse checkout pane by default.'),
- '#default_value' => variable_get('uc_coupon_collapse_pane', FALSE),
- );
- $form['uc_coupon_pane_description'] = array(
- '#type' => 'textarea',
- '#title' => t('Checkout pane message'),
- '#default_value' => variable_get('uc_coupon_pane_description', t('Enter a coupon code for this order.')),
- );
- return $form;
- }
- }
- /**
- * Submit handler for the uc_coupon form.
- */
- function uc_coupon_form_submit($form, &$form_state) {
- $trigger = $form_state['triggering_element']['#name'];
- // Determine where the values are (they will be in a subarray if called from checkout or order page).
- switch ($context = $form['#uc_coupon_form_context']) {
- case 'checkout':
- $values = $form_state['values']['panes']['coupon'];
- break;
- case 'order':
- $values = $form_state['values']['coupon'];
- break;
- default:
- $values = $form_state['values'];
- }
- // If this was the result of a 'remove' submission.
- if (substr($trigger, 0, 16) == 'uc-coupon-remove') {
- // See if there was an individual remove button clicked.
- $code = substr($trigger, 17);
- if (!empty($code)) {
- if ($context == 'order') {
- unset($form_state['order']->data['coupons']['code']);
- }
- else {
- uc_coupon_session_clear($code);
- drupal_set_message(t('Coupon "%code" has been removed from your order', array('%code' => $code)));
- module_invoke_all('uc_coupon_remove', uc_coupon_find($code));
- }
- }
- // Otherwise see if it's a checkbox submission.
- elseif (isset($values['coupons'])) {
- $removed = array();
- foreach ($values['coupons'] as $code => $selected) {
- if (!$selected) {
- $removed[] = $code;
- if ($context == 'order') {
- unset($form_state['order']->data['coupons'][$code]);
- }
- else {
- uc_coupon_session_clear($code);
- module_invoke_all('uc_coupon_remove', uc_coupon_find($code));
- }
- }
- }
- $n = count($removed);
- if ($n > 1) {
- $last = $removed[$n - 1];
- $rest = implode(', ', array_slice($removed, 0, $n - 1));
- drupal_set_message(t('Coupons %rest and %last have been removed from your order.', array('%rest' => $rest, '%last' => $last)));
- }
- elseif (!empty($removed)) {
- drupal_set_message(t('Coupon %code has been removed from your order', array('%code' => $removed[0])));
- }
- }
- }
- // Otherwise try to apply.
- else {
- $code = empty($values['code']) ? '' : strtoupper(trim($values['code']));
- $removed = FALSE;
- // If multiple codes are not enabled, then remove any codes currently applied.
- if (!variable_get('uc_coupon_allow_multiple', FALSE) && count($session = uc_coupon_session_get()) > 0) {
- if ($context == 'order') {
- unset($form_state['order']->data['coupons']);
- }
- else {
- foreach (array_keys($session) as $remove_code) {
- uc_coupon_session_clear($remove_code);
- drupal_set_message(t('Coupon "%code" has been removed from your order', array('%code' => $remove_code)));
- module_invoke_all('uc_coupon_remove', uc_coupon_find($remove_code));
- }
- $removed = TRUE;
- }
- }
- if (!empty($code)) {
- if ($context == 'order') {
- $coupon = uc_coupon_validate($code, $form_state['order'], user_load($form_state['order']->uid));
- if ($coupon->valid) {
- $form_state['order']->data['coupons'][$code] = $coupon->discounts;
- }
- else {
- drupal_set_message($coupon->message, 'error');
- }
- }
- else {
- uc_coupon_session_add($code, 'submit');
- }
- }
- elseif (!$removed) {
- drupal_set_message(t("You must enter a valid coupon code."), 'error');
- }
- }
- }
- /**
- * Implements hook_uc_line_item().
- */
- function uc_coupon_uc_line_item() {
- $items[] = array(
- 'id' => 'coupon',
- 'title' => t('Coupon discount'),
- 'tax_adjustment' => 'uc_coupon_tax_adjustment',
- 'weight' => 0,
- 'default' => FALSE,
- 'stored' => TRUE,
- 'add_list' => FALSE,
- 'calculated' => TRUE,
- );
- return $items;
- }
- /**
- * Handle tax on coupons by calculating tax for individual discounted prices.
- */
- function uc_coupon_tax_adjustment($price, $order, $tax) {
- $amount = 0;
- if (isset($order->data['coupons'])) {
- foreach ($order->data['coupons'] as $discounts) {
- foreach ($discounts as $id => $item) {
- if (is_numeric($id) && $id > 0) {
- // This is a product discount, so see if the product is taxable.
- $node = node_load($id);
- $adjust = in_array($node->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $node->shippable == 1);
- }
- else {
- // This is a line-item discount, so find the corresponding line item.
- $lid = is_numeric($id) ? -$id : $id; // Convert id to a line item id.
- foreach ($order->line_items as $line_item) {
- if ($line_item['line_item_id'] == $lid) {
- $adjust = in_array($line_item['type'], $tax->taxed_line_items);
- break;
- }
- }
- }
- if ($adjust) {
- $amount += (isset($item->pretax_discount) ? $item->pretax_discount : $item->discount) * ($price > 0 ? 1 : -1);
- }
- }
- }
- }
- return $amount;
- }
- /**
- * Show a message if PayPal is enabled and "itemized order" is selected.
- */
- function _uc_coupon_paypal_check() {
- if (variable_get('uc_payment_method_paypal_wps_checkout', 0) && variable_get('uc_paypal_wps_submit_method', 'single') == 'itemized') {
- drupal_set_message(t('To use coupons with PayPal you must select "Submit the whole order as a single line item". <a href="!url">Click here to change this setting</a>.', array('!url' => url('admin/store/settings/payment/edit/methods'))));
- }
- }
- /**
- * Implements hook_uc_store_status().
- */
- function uc_coupon_uc_store_status() {
- $statuses = array();
- if (variable_get('uc_payment_method_paypal_wps_checkout', 0) && variable_get('uc_paypal_wps_submit_method', 'single') == 'itemized') {
- $statuses[] = array(
- 'status' => 'warning',
- 'title' => t('Coupons'),
- 'desc' => t('To use coupons with PayPal you must select "Submit the whole order as a single line item". <a href="!url">Click here to change this setting</a>.', array('!url' => url('admin/store/settings/payment/edit/methods'))),
- );
- }
- return $statuses;
- }
- /**
- * Implements hook_uc_cart_alter().
- *
- * This is called every time the cart is rebuild (e.g. when products are added), so it's a good place
- * to revalidate our session coupons. We also add a fake cart item (if configured to show in cart)
- * for each coupon. These will be removed at checkout.
- */
- function uc_coupon_uc_cart_alter(&$items) {
- // Validate all codes in the session against the cart contents.
- $order = new UcOrder();
- $order->products = $items;
- $order->data = array();
- $coupons = uc_coupon_session_validate($order);
- if (variable_get('uc_coupon_show_in_cart', TRUE) && !empty($coupons)) {
- // If there are some valid coupons, then add them to the cart (but only if
- // they have a non-zero value.
- foreach ($coupons as $code => $coupon) {
- if ($coupon->amount != 0) {
- $items[] = _uc_coupon_cart_item($coupon);
- }
- }
- }
- }
- /**
- * Creates a fake cart-item corrresponding to this coupon, allowing this coupon to be displayed in the cart.
- *
- * @param $coupon
- * The coupon to be displayed in the cart.
- */
- function _uc_coupon_cart_item($coupon) {
- // Exclude any line-item discounts from the amount shown in the cart.
- $amount = 0;
- foreach ($coupon->discounts as $id => $discount) {
- if (is_numeric($id) && $id > 0) {
- $amount += $discount->discount;
- }
- }
- // Assign this a unique cart_item_id so it will be keyed properly by entity_view().
- $id = -hexdec(substr(sha1($coupon->code), -8));
- return (object) array(
- 'cart_item_id' => $id,
- 'module' => 'uc_coupon',
- 'title' => $coupon->title,
- 'nid' => 0,
- 'qty' => 1,
- 'price' => -$amount,
- 'data' => array('module' => 'uc_coupon', 'shippable' => FALSE, 'code' => $coupon->code, 'remove' => uc_coupon_session_get($coupon->code)),
- 'model' => 0,
- 'weight' => 0
- );
- }
- /**
- * Implements hook_uc_cart_display().
- */
- function uc_coupon_uc_cart_display($item) {
- $display_item = array(
- 'module' => array('#type' => 'value', '#value' => 'uc_coupon'),
- 'nid' => array('#type' => 'value', '#value' => 0),
- 'title' => array('#markup' => $item->title),
- 'description' => array('#markup' => ''),
- 'qty' => array('#type' => 'hidden', '#value' => 1, '#default_value' => 1),
- '#total' => $item->price,
- 'data' => array('#type' => 'hidden', '#value' => serialize($item->data)),
- '#suffixes' => array(),
- );
- if ($item->data['remove']) {
- $display_item['remove'] = array('#type' => 'submit', '#value' => t('Remove'));
- }
- return $display_item;
- }
- /**
- * Implements hook_uc_update_cart_item().
- * Remove a coupon from the order when the "Remove" button is clicked.
- */
- function uc_coupon_uc_update_cart_item($nid, $data, $qty) {
- if (isset($data['code']) && $qty == 0) {
- uc_coupon_session_clear($data['code']);
- module_invoke_all('uc_coupon_remove', uc_coupon_find($data['code']));
- }
- }
- /**
- * Theme override for the default cart block content.
- * Removes coupons from the total number of items.
- */
- function uc_coupon_theme_uc_cart_block_content($variables) {
- if (variable_get('uc_coupon_show_in_cart', TRUE) && !empty($variables['items'])) {
- foreach ($variables['items'] as &$item) {
- if ($item['nid'] == 0 && $item['price'] <= 0) {
- $item['qty'] = '';
- $variables['item_count']--;
- }
- }
- $variables['item_text'] = format_plural($variables['item_count'], '<span class="num-items">1</span> Item', '<span class="num-items">@count</span> Items');
- }
- return theme_uc_cart_block_content($variables);
- }
- /**
- * Implements hook_form_FORM_ID_alter() for uc_cart_checkout_form().
- *
- * Remove any coupon cart items from the serialized cart contents and payment-pane
- * order, as coupons will be handled as line items during checkout.
- *
- * Collapse coupon checkout pane, if configured to do so.
- */
- function uc_coupon_form_uc_cart_checkout_form_alter(&$form, $form_state) {
- if (variable_get('uc_coupon_collapse_pane', FALSE) && isset($form['panes']['coupon'])) {
- $form['panes']['coupon']['#collapsed'] = TRUE;
- }
- // Show current session coupons in the cart pane (since now they will have been removed from the order).
- if (variable_get('uc_coupon_show_in_cart', TRUE) && isset($form['panes']['cart'])) {
- $coupons = uc_coupon_session_validate();
- // If there are some valid coupons, then add them to the cart.
- foreach ($coupons as $code => $coupon) {
- if ($coupon->amount != 0) {
- $item = _uc_coupon_cart_item($coupon);
- $item->order_product_id = $item->cart_item_id;
- $form['panes']['cart']['cart_review_table']['#items'][] = $item;
- }
- }
- }
- }
- /**
- * Implements hook_uc_checkout_complete().
- *
- * Ensure the stored coupon code is reset after checkout.
- */
- function uc_coupon_uc_checkout_complete($order, $account) {
- uc_coupon_session_clear();
- }
- /**
- * Preprocess template for a printed coupon certificate.
- * @see uc_coupon-certificate.tpl.php
- */
- function template_preprocess_uc_coupon_certificate(&$variables) {
- $coupon = $variables['coupon'];
- // Create variables for each user-added field.
- $fields = field_info_fields();
- foreach ($fields as $name => $field) {
- if (in_array('uc_coupon', array_keys($field['bundles']))) {
- $items = field_get_items('uc_coupon', $coupon, $name);
- $variables[$name] = $items;
- }
- }
- $variables['value'] = theme('uc_coupon_discount', array('coupon' => $coupon));
- $variables['display_name'] = check_plain($coupon->name);
- $n = stripos($variables['display_name'], 'purchased by');
- if ($n) {
- $variables['display_name'] = substr($variables['display_name'], 0, $n -1);
- }
- if ($coupon->valid_until) {
- $variables['not_yet_valid'] = $coupon->valid_from > REQUEST_TIME;
- $variables['valid_from'] = format_date($coupon->valid_from, 'custom', variable_get('date_format_uc_store', 'm/d/Y'));
- $variables['valid_until'] = format_date($coupon->valid_until, 'custom', variable_get('date_format_uc_store', 'm/d/Y'));
- }
- else {
- $variables['not_yet_valid'] = FALSE;
- $variables['valid_from'] = FALSE;
- $variables['valid_until'] = FALSE;
- }
- $variables['max_uses_per_user'] = isset($coupon->data['max_uses_per_user']) ? $coupon->data['max_uses_per_user'] : NULL;
- $variables['include'] = array();
- $variables['exclude'] = array();
- if (isset($coupon->data['product_types'])) {
- foreach ($coupon->data['product_types'] as $type) {
- $variables['include'][] = node_type_get_name($type);
- }
- }
- if (isset($coupon->data['products'])) {
- $key = isset($coupon->data['negate_products']) ? 'exclude' : 'include';
- foreach ($coupon->data['products'] as $nid) {
- $node = node_load($nid);
- $variables[$key][] = $node->title;
- }
- }
- if (isset($coupon->data['skus'])) {
- foreach ($coupon->data['skus'] as $sku) {
- $variables['include'][] = t('SKU') . ' ' . $sku;
- }
- }
- if (isset($coupon->data['terms'])) {
- $key = isset($coupon->data['negate_terms']) ? 'exclude' : 'include';
- foreach ($coupon->data['terms'] as $tid) {
- $term = taxonomy_term_load($tid);
- $variables[$key][] = $term->name;
- }
- }
- // Merge in global tokens.
- $info = token_info();
- foreach ($info['types'] as $type => $type_info) {
- if (empty($type_info['needs-data']) && $type != 'current-user' && $type != 'current-date') {
- $type_key = !empty($type_info['type']) ? $type_info['type'] : $type;
- if (!empty($info['tokens'][$type_key])) {
- foreach (array_keys($info['tokens'][$type_key]) as $token) {
- $variables[str_replace('-', '_', $type_key) . '_' . str_replace('-', '_', $token)] = token_replace("[$type_key:$token]");
- }
- }
- }
- }
- if (isset($variables['coupon']->data['base_cid'])) {
- $variables['theme_hook_suggestions'][] = 'uc_coupon_certificate__base_' . $variables['coupon']->data['base_cid'];
- }
- $variables['theme_hook_suggestions'][] = 'uc_coupon_certificate__' . $variables['coupon']->cid;
- }
- /**
- * Page template for printed coupons.
- * @see uc_coupon-page.tpl.php
- */
- function template_preprocess_uc_coupon_page(&$variables) {
- $variables['styles'] = drupal_get_css();
- }
- /**
- * Implements hook_uc_coupon_actions().
- */
- function uc_coupon_uc_coupon_actions($coupon) {
- $actions = array();
- if (user_access('view store coupons')) {
- $actions[] = array(
- 'url' => 'admin/store/coupons/' . $coupon->cid,
- 'icon' => drupal_get_path('module', 'uc_store') . '/images/order_view.gif',
- 'title' => t('View coupon: @name', array('@name' => $coupon->name)),
- );
- $actions[] = array(
- 'url' => 'admin/store/coupons/' . $coupon->cid . '/print',
- 'icon' => drupal_get_path('module', 'uc_store') . '/images/print.gif',
- 'title' => t('Print coupon: @name', array('@name' => $coupon->name)),
- );
- if ($coupon->bulk) {
- $actions[] = array(
- 'url' => 'admin/store/coupons/' . $coupon->cid . '/codes',
- 'icon' => drupal_get_path('module', 'uc_store') . '/images/menu_reports_small.gif',
- 'title' => t('Download codes as CSV: @name', array('@name' => $coupon->name)),
- );
- }
- }
- if (user_access('manage store coupons')) {
- $actions[] = array(
- 'url' => 'admin/store/coupons/' . $coupon->cid . '/edit',
- 'icon' => drupal_get_path('module', 'uc_store') . '/images/order_edit.gif',
- 'title' => t('Edit coupon: @name', array('@name' => $coupon->name)),
- );
- $actions[] = array(
- 'url' => 'admin/store/coupons/' . $coupon->cid . '/delete',
- 'icon' => drupal_get_path('module', 'uc_store') . '/images/order_delete.gif',
- 'title' => t('Delete coupon: @name', array('@name' => $coupon->name)),
- );
- }
- return $actions;
- }
- /**
- * Implements hook_views_api().
- */
- function uc_coupon_views_api() {
- return array(
- 'api' => '2.0',
- 'path' => drupal_get_path('module', 'uc_coupon') . '/views',
- );
- }
- /**
- * Check whether an order is the order being checked out by the current user.
- * @param $order
- */
- function _uc_coupon_is_checkout_order($order) {
- global $user;
- return isset($_SESSION['cart_order'])
- && isset($order->order_id)
- && $order->order_id == $_SESSION['cart_order']
- && uc_order_status_data($order->order_status, 'state') == 'in_checkout'
- && $user->uid == $order->uid;
- }
- /**
- * Implements hook_entity_info();
- */
- function uc_coupon_entity_info() {
- return array(
- 'uc_coupon' => array(
- 'label' => t('Coupon'),
- 'controller class' => 'UcCouponController',
- 'metadata controller class' => 'UcCouponMetadataController',
- 'base table' => 'uc_coupons',
- 'fieldable' => TRUE,
- 'entity keys' => array(
- 'id' => 'cid',
- ),
- 'bundles' => array(
- 'uc_coupon' => array(
- 'label' => t('Coupon'),
- 'admin' => array(
- 'path' => 'admin/store/settings/coupon',
- 'access arguments' => array('manage store coupons'),
- ),
- ),
- ),
- 'view modes' => array(
- 'full' => array(
- 'label' => t('Administrative view'),
- ),
- ),
- ),
- );
- }
- /**
- * Loads one coupon entity from the database.
- */
- function uc_coupon_load($cid, $reset = FALSE) {
- if (is_null($cid) || $cid < 1) {
- return FALSE;
- }
- $coupons = uc_coupon_load_multiple(array($cid), array(), $reset);
- return $coupons ? reset($coupons) : FALSE;
- }
- /**
- * Loads one or more coupon entities from the database.
- *
- * @param $ids
- * An array of coupon IDs.
- * @param $conditions
- * An array of conditions on the {uc_coupons} table in the form
- * 'field' => $value.
- *
- * @return
- * An array of order objects indexed by order_id.
- */
- function uc_coupon_load_multiple($ids, $conditions = array(), $reset = FALSE) {
- return entity_load('uc_coupon', $ids, $conditions, $reset);
- }
- /**
- * Save a coupon object.
- *
- * If the 'cid' field is set, then this will update an existing coupon.
- * Otherwise, a new bulk seed will be generated, the coupon will be
- * inserted into the database, and $coupon->cid will be set.
- *
- * @param $coupon
- * The coupon to save.
- *
- * @param $edit
- * An optional array of extra data that other modules may need to save.
- */
- function uc_coupon_save(&$coupon, $edit = array()) {
- entity_save('uc_coupon', $coupon);
- }
- /**
- * Delete a coupon object.
- *
- * @param $cid
- * The id of the coupon to delete.
- */
- function uc_coupon_delete($cid) {
- entity_delete('uc_coupon', $cid);
- }
- /**
- * Implements hook_field_extra_fields().
- */
- function uc_coupon_field_extra_fields() {
- $extra = array();
- $extra['uc_coupon']['uc_coupon']['display']['admin_summary'] = array(
- 'label' => t('Administrative Summary'),
- 'description' => t('A summary of all coupon details.'),
- 'weight' => 0,
- );
- return $extra;
- }
- /**
- * Implements hook_uc_order_pane().
- *
- * Defines the shipping quote order pane.
- */
- function uc_coupon_uc_order_pane() {
- $panes['coupon'] = array(
- 'callback' => 'uc_order_pane_coupon',
- 'title' => t('Coupon, Credit or Discount Codes'),
- 'desc' => t('Apply a coupon or discount code to the current order.'),
- 'class' => 'pos-left',
- 'weight' => 7,
- 'show' => array('edit'),
- );
- return $panes;
- }
- /**
- * Coupon order pane callback.
- *
- * @see uc_quote_order_pane_quotes_submit()
- * @see uc_quote_apply_quote_to_order()
- */
- function uc_order_pane_coupon($op, $order, &$form = NULL, &$form_state = NULL) {
- switch ($op) {
- case 'edit-form':
- $submit = array(
- '#limit_validation_errors' => array(array('coupon')),
- '#submit' => array('uc_coupon_order_submit'),
- );
- $form['coupon'] = uc_coupon_form(array(), $form_state, 'order', $submit);
- $form['#uc_coupon_form_context'] = 'order';
- $form['coupon']['#theme'] = 'uc_coupon_form';
- $form['coupon']['#tree'] = TRUE;
- break;
- case 'edit-theme':
- return drupal_render($form['coupon']);
- }
- }
- /**
- * Implements hook_form_uc_order_edit_form_alter().
- */
- function uc_coupon_form_uc_order_edit_form_alter(&$form, &$form_state) {
- $order = $form_state['order'];
- $line_items = $order->line_items;
- foreach ($line_items as $item) {
- // Coupon line items should be changed using the coupon order-edit pane.
- if ($item['type'] == 'coupon') {
- $form['line_items'][$item['line_item_id']]['title'] = array(
- '#markup' => check_plain($item['title']),
- );
- $form['line_items'][$item['line_item_id']]['remove']['#access'] = FALSE;
- $form['line_items'][$item['line_item_id']]['amount'] = array(
- '#theme' => 'uc_price',
- '#price' => $item['amount'],
- );
- }
- }
- }
- /**
- * Gets the fully validated coupon objects that have been applied to this order.
- *
- * @param $order
- * The order in question.
- * @param $recalculate
- * If TRUE, the value of each coupon will be recalculated using the current state
- * of the order and current coupon settings.
- * If FALSE (default), the original coupon values will be preserved.
- */
- function uc_coupon_get_order_coupons($order, $recalculate = FALSE) {
- $coupons = array();
- if (!empty($order->data['coupons'])) {
- if ($recalculate) {
- $dummy_order = clone $order;
- $dummy_order->data['coupons'] = array();
- }
- foreach ($order->data['coupons'] as $code => $discounts) {
- $coupon = uc_coupon_find($code);
- if (!empty($coupon->cid)) {
- if ($recalculate) {
- $discounts = uc_coupon_calculate_discounts($coupon, $dummy_order);
- $dummy_order->data['coupons'][$code] = $coupon->discounts;
- }
- $coupons[] = uc_coupon_prepare($coupon, $code, $discounts);
- }
- }
- }
- return $coupons;
- }
|