
Title:		Specials valid from
Version:	1.0.3 (14.10.2004)
Author:		Felix Schwarz (Felix.Schwarz@grede.de), Grede.de Werbeagentur GmbH
License:	GPL (see php files for more details)

Homepage: http://wiki.felix-schwarz.info/OsCommerce
Please check the page above for more recent versions than this one.


What does it do?
With the current version of osCommerce you can specify when a special offer should
expire but you can't specify when it should appear in the shop. With this contribution
you can specify start and expiry date for every special.

Future Work:
	At the moment the contribution doesn't check for two overlapping times with the same
	special. This should be checked (maybe ask user what to do).

Changes:
	Database changes:	1
	new files:		0
	changed files:		6


Install instructions:
1) Backup your osCommerce files NOW! Backup your database!

2) Alter your database (e.g. with PHPMyAdmin or command line) so that there is
a place to store the start date.
ALTER TABLE `specials` ADD `valid_from_date` DATETIME DEFAULT NULL;

3) Install the contribution - there are two ways installing the contribution.
I recommend using option B allthough this seem a bit strange to you if you are a Windows user.

A) Extremly fast installation

    If you didn't install a contribution that modified
    catalog/admin/specials.php, the specials.php language files in catalog/admin/includes/languages,
    catalog/admin/includes/javascript/calendarcode.js or catalog/includes/functions/specials.php
    you may simply copy the new admin directory (the one that is created after extracting the zip
    of this contribution) over the old one and replace changed files.

    That's it.

B) Automated installation / "The Un*x way" (strongly recommended)

  This option includes using the command line. It is far superior to method C because
  everything is automated so you can't introduce new errors. Patch can even handle
  slight modifications in your osCommerce installation.

  * Get a version of the "patch" program for your system (if you are using Linux
    or something similar, it is probably already installed - try "patch --version").
    Windows users can get a copy when installing Cygwin (www.cygwin.com).

  * put the patch file included in this zip into catalog/

  * go into catalog/admin and type "patch -p1 < specials_valid_from.patch" on the command line.

  * You're done!

  * If you are using Windows (NT, 2000, XP) and experiencing the following error
  message "No input file specified." after browsing to specials.php the patch tool destroyed
  some permissions - please set the permissions for SYSTEM to full access.


C) Installing manually / "the hard and error prone way"
You'll have to use this way if you proviously modified your installation of osCommerce so
that patch won't be able to detect what to change. Usually this means that you have to
edit PHP code (not only replace or insert some statements) so please check again if your backup
is up to date.

edit catalog\admin\includes\languages\german\specials.php
replace (line 23)
define('TEXT_SPECIALS_PRICE_TIP', '<b>Bemerkung:</b><ul><li>Sie k&ouml;nnen im Feld Angebotspreis auch prozentuale Werte angeben, z.B.: <b>20%</b></li><li>Wenn Sie einen neuen Preis eingeben, m&uuml;ssen die Nachkommastellen mit einem \'.\' getrennt werden, z.B.: <b>49.99</b></li><li>Lassen Sie das Feld <b>\'Gltig bis\'</b> leer, wenn der Angebotspreis zeitlich unbegrenzt gelten soll.</li></ul>');

with:
// Specials valid from START
define('TEXT_SPECIALS_PRICE_TIP', '<b>Bemerkung:</b><ul><li>Sie k&ouml;nnen im Feld Angebotspreis auch prozentuale Werte angeben, z.B.: <b>20%</b></li><li>Wenn Sie einen neuen Preis eingeben, m&uuml;ssen die Nachkommastellen mit einem \'.\' getrennt werden, z.B.: <b>49.99</b></li><li>Lassen Sie das Feld <b>\'Gltig bis\'</b> leer, wenn der Angebotspreis zeitlich unbegrenzt gelten soll.</li><li>Lassen Sie das Feld <b>\'Gltig ab\'</b> leer, wenn der Angebotspreis ab sofort gelten soll.</li></ul>');
// Specials valid from END

insert before the last '?>'
// Specials valid from START
define('TEXT_INFO_VALIDFROM_DATE', 'G&uuml;ltig ab:');
define('TEXT_SPECIALS_VALIDFROM_DATE', 'G&uuml;ltig ab:<br><small>(tt.mm.jjjj)</small>');
// Specials valid from END


edit catalog\admin\includes\languages\english\specials.php
replace (line 23)
define('TEXT_SPECIALS_PRICE_TIP', '<b>Specials Notes:</b><ul><li>You can enter a percentage to deduct in the Specials Price field, for example: <b>20%</b></li><li>If you enter a new price, the decimal separator must be a \'.\' (decimal-point), example: <b>49.99</b></li><li>Leave the expiry date empty for no expiration</li></ul>');

with:
// Specials valid from START
define('TEXT_SPECIALS_PRICE_TIP', '<b>Specials Notes:</b><ul><li>You can enter a percentage to deduct in the Specials Price field, for example: <b>20%</b></li><li>If you enter a new price, the decimal separator must be a \'.\' (decimal-point), example: <b>49.99</b></li><li>Leave the expiry date empty for no expiration.</li><li>Leave the <b>\'valid from date\'</b> empty if the offer should be valid immediately.</li></ul>');
// Specials valid from END

insert before the last '?>'
// Specials valid from START
define('TEXT_INFO_VALIDFROM_DATE', 'valid from:');
define('TEXT_SPECIALS_VALIDFROM_DATE', 'valid from:<br><small>(dd.mm.yyyy)</small>');
// Specials valid from END


edit catalog\admin\includes\languages\espanol\specials.php
replace (line 23)
define('TEXT_SPECIALS_PRICE_TIP', '<b>Notas:</b><ul><li>Puedes introducir un porcentaje de reduccion del precio del producto, por ejemplo: <b>20%</b></li><li>Si por el contrario, introduces un precio de oferta debes de usar el punto como separador decimal \'.\' (punto decimal), por ejemplo: <b>49.99</b></li><li>Deja la fecha de caducidad vacia si no quieres caducidad</li></ul>');

with:
// Specials valid from START
define('TEXT_SPECIALS_PRICE_TIP', '<b>Notas:</b><ul><li>Puedes introducir un porcentaje de reduccion del precio del producto, por ejemplo: <b>20%</b></li><li>Si por el contrario, introduces un precio de oferta debes de usar el punto como separador decimal \'.\' (punto decimal), por ejemplo: <b>49.99</b></li><li>Deja la fecha de caducidad vacia si no quieres caducidad</li><li>Leave the <b>\'valid from date\'</b> empty if the offer should be valid immediately.</li></ul>');
// Specials valid from END

insert before the last '?>'
// Specials valid from START
define('TEXT_INFO_VALIDFROM_DATE', 'valid from:');
define('TEXT_SPECIALS_VALIDFROM_DATE', 'valid from:<br><small>(dd.mm.yyyy)</small>');
// Specials valid from END



// active specials according to their valid from date
edit catalog\includes\functions\specials.php
replace the complete function (line 21)
////
// Auto expire products on special
  function tep_expire_specials() {
    $specials_query = tep_db_query("select specials_id from " . TABLE_SPECIALS . " where status = '1' and now() >= expires_date and expires_date > 0");
    if (tep_db_num_rows($specials_query)) {
      while ($specials = tep_db_fetch_array($specials_query)) {
        tep_set_specials_status($specials['specials_id'], '0');
      }
    }
  }

with:
////
// Auto expire products on special
  function tep_expire_specials() {
    $specials_query = tep_db_query("select specials_id from " . TABLE_SPECIALS . " where status = '1' and now() >= expires_date and expires_date > 0");
    if (tep_db_num_rows($specials_query)) {
      while ($specials = tep_db_fetch_array($specials_query)) {
        tep_set_specials_status($specials['specials_id'], '0');
      }
    }

    $specials_query = tep_db_query("select specials_id from " . TABLE_SPECIALS . " where status = '0' and now() >= valid_from_date and now() < expires_date and expires_date > 0");
    if (tep_db_num_rows($specials_query)) {
      while ($specials = tep_db_fetch_array($specials_query)) {
        tep_set_specials_status($specials['specials_id'], '1');
      }
    }
  }


edit catalog\admin\includes\javascript\calendercode.js
replace (line 18)
var ppcY = 4;

with:
var ppcY = 4;

var dayfield;
var monthfield;
var yearfield;


replace (line 82)
function showCalendar(frmName, dteBox,btnImg, hideDrops, MnDt, MnMo, MnYr, MxDt, MxMo, MxYr,runFuncs) {
    hideDropDowns = hideDrops;
    FuncsToRun = runFuncs;
    calfrmName = frmName;

with:
function showCalendar(frmName, day, month, year, dteBox,btnImg, hideDrops, MnDt, MnMo, MnYr, MxDt, MxMo, MxYr,runFuncs) {
    hideDropDowns = hideDrops;
    FuncsToRun = runFuncs;
    calfrmName = frmName;

    dayfield = day;
    monthfield = month;
    yearfield = year;


replace (line 94)
    else {

with:
    else {
    	if (dayfield == null || monthfield == null || yearfield == null) { alert ("Your specials.php was patched incorrectly. Look especially for all calls of showCalendar and if they have at least 6 parameters.");}




replace (line 389):
    eval('document.' + calfrmName + '.vday.value = "'+ padout(curDate.getDate()) + '"');
    eval('document.' + calfrmName + '.vmonth.value = "'+ padout(curDate.getMonth()+1) + '"');
    eval('document.' + calfrmName + '.vyear.value = "'+ curDate.getFullYear() + '"');

with:
    eval('document.' + calfrmName + '.'+ dayfield +'.value = "'+ padout(curDate.getDate()) + '"');
    eval('document.' + calfrmName + '.'+ monthfield +'.value = "'+ padout(curDate.getMonth()+1) + '"');
    eval('document.' + calfrmName + '.'+ yearfield +'.value = "'+ curDate.getFullYear() + '"');


Now you have to change all calls to showCalendar() as we added three new parameters. If you did not
install other contributions that are using showCalender there should be no occurences of showCalendar
other than in specials.php.

Modifiying other showCalendar() calls is quite simple. Just look at the following example:
Given the original call: "showCalendar(1stParameter, 2ndParameter, 3rdParameter, ...)"
After editing: "showCalendar(1stParameter, 'day', 'month', 'year', 2ndParameter, 3rdParameter, ...)"



edit catalog\admin\specials.php
replace (line 31)
        $day = tep_db_prepare_input($HTTP_POST_VARS['day']);
        $month = tep_db_prepare_input($HTTP_POST_VARS['month']);
        $year = tep_db_prepare_input($HTTP_POST_VARS['year']);
with:
        $day = tep_db_prepare_input($HTTP_POST_VARS['day']);
        $month = tep_db_prepare_input($HTTP_POST_VARS['month']);
        $year = tep_db_prepare_input($HTTP_POST_VARS['year']);
        $vday = tep_db_prepare_input($HTTP_POST_VARS['vday']);
        $vmonth = tep_db_prepare_input($HTTP_POST_VARS['vmonth']);
        $vyear = tep_db_prepare_input($HTTP_POST_VARS['vyear']);

        // check if valid from date is later than expires date
        if (tep_not_null($day) && tep_not_null($month) && tep_not_null($year) &&
        	tep_not_null($vday) && tep_not_null($vmonth) && tep_not_null($vyear)) {

        	if (($vyear > $year) ||
        		($vmonth > $month && $vyear == $year) ||
        		($vday > $day && $vmonth == $month && $vyear == $year))
        		{
        			// if so, swap the dates
        			$tempdate = $vyear; $vyear = $year; $year = $tempdate;
        			$tempdate = $month; $vmonth = $month; $month = $tempdate;
        			$tempdate = $vday; $vday = $day; $day = $tempdate;
        		}
        }



insert after line 62:
        if (tep_not_null($day) && tep_not_null($month) && tep_not_null($year)) {
          $expires_date = $year;
          $expires_date .= (strlen($month) == 1) ? '0' . $month : $month;
          $expires_date .= (strlen($day) == 1) ? '0' . $day : $day;
        }

insert:
        $validfrom_date = '';
        if (tep_not_null($vday) && tep_not_null($vmonth) && tep_not_null($vyear)) {
          $validfrom_date = $vyear;
          $validfrom_date .= (strlen($vmonth) == 1) ? '0' . $vmonth : $vmonth;
          $validfrom_date .= (strlen($vday) == 1) ? '0' . $vday : $vday;
        }


// set the new valid from field and activate the special accordingly if a new special was created
replace line 75
        tep_db_query("insert into " . TABLE_SPECIALS . " (products_id, specials_new_products_price, specials_date_added, expires_date, status) values ('" . (int)$products_id . "', '" . tep_db_input($specials_price) . "', now(), '" . tep_db_input($expires_date) . "', '1')");

with:
        $result = tep_db_query("insert into " . TABLE_SPECIALS . " (products_id, specials_new_products_price, specials_date_added, expires_date, status, valid_from_date) values ('" . (int)$products_id . "', '" . tep_db_input($specials_price) . "', now(), '" . tep_db_input($expires_date) . "', '1', '".  tep_db_input($validfrom_date)  ."')");

        // maybe the special product must be deactivated or actived (depending on wether the valid from/expires dates had been changed)
        if ($result == 1) {
        	$new_specials_id = tep_db_insert_id();
        	tep_db_query("update " . TABLE_SPECIALS . " set status = '0' where (now() < valid_from_date or (now() >= expires_date and expires_date > 0)) and specials_id = '" . (int)$specials_id . "'");
        	tep_db_query("update " . TABLE_SPECIALS . " set status = '1' where (now() >= valid_from_date and now() < expires_date) and specials_id = '" . (int)$new_specials_id . "'");
        }



// set the new valid from field and activate the special accordingly if a special was updated
insert after line 90:
        $day = tep_db_prepare_input($HTTP_POST_VARS['day']);
        $month = tep_db_prepare_input($HTTP_POST_VARS['month']);
        $year = tep_db_prepare_input($HTTP_POST_VARS['year']);

insert:
        $vday = tep_db_prepare_input($HTTP_POST_VARS['vday']);
        $vmonth = tep_db_prepare_input($HTTP_POST_VARS['vmonth']);
        $vyear = tep_db_prepare_input($HTTP_POST_VARS['vyear']);

        // check if valid from date is later than expires date
        if (tep_not_null($day) && tep_not_null($month) && tep_not_null($year) &&
        	tep_not_null($vday) && tep_not_null($vmonth) && tep_not_null($vyear)) {

        	if (($vyear > $year) ||
        		($vmonth > $month && $vyear == $year) ||
        		($vday > $day && $vmonth == $month && $vyear == $year))
        		{
        			// if so, swap the dates
        			$tempdate = $vyear; $vyear = $year; $year = $tempdate;
        			$tempdate = $month; $vmonth = $month; $month = $tempdate;
        			$tempdate = $vday; $vday = $day; $day = $tempdate;
        		}
        }


insert after line 112:
        if (tep_not_null($day) && tep_not_null($month) && tep_not_null($year)) {
          $expires_date = $year;
          $expires_date .= (strlen($month) == 1) ? '0' . $month : $month;
          $expires_date .= (strlen($day) == 1) ? '0' . $day : $day;
        }

insert:
        $validfrom_date = '';
        if (tep_not_null($vday) && tep_not_null($vmonth) && tep_not_null($vyear)) {
          $validfrom_date = $vyear;
          $validfrom_date .= (strlen($vmonth) == 1) ? '0' . $vmonth : $vmonth;
          $validfrom_date .= (strlen($vday) == 1) ? '0' . $vday : $vday;
        }


replace (line 125)
        tep_db_query("update " . TABLE_SPECIALS . " set specials_new_products_price = '" . tep_db_input($specials_price) . "', specials_last_modified = now(), expires_date = '" . tep_db_input($expires_date) . "' where specials_id = '" . (int)$specials_id . "'");

with:
        $result = tep_db_query("insert into " . TABLE_SPECIALS . " (products_id, specials_new_products_price, specials_date_added, expires_date, status, valid_from_date) values ('" . (int)$products_id . "', '" . tep_db_input($specials_price) . "', now(), '" . tep_db_input($expires_date) . "', '1', '".  tep_db_input($validfrom_date)  ."')");

        // maybe the special product must be deactivated or actived (depending on wether the valid from/expires dates had been changed)
        if ($result == 1) {
        	$new_specials_id = tep_db_insert_id();
        	tep_db_query("update " . TABLE_SPECIALS . " set status = '0' where (now() < valid_from_date or (now() >= expires_date and expires_date > 0)) and specials_id = '" . (int)$new_specials_id . "'");
        	tep_db_query("update " . TABLE_SPECIALS . " set status = '1' where (now() >= valid_from_date and now() < expires_date) and specials_id = '" . (int)$new_specials_id . "'");
        }


replace (line 192)
      $product_query = tep_db_query("select p.products_id, pd.products_name, p.products_price, s.specials_new_products_price, s.expires_date from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_SPECIALS . " s where p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "' and p.products_id = s.products_id and s.specials_id = '" . (int)$HTTP_GET_VARS['sID'] . "'");

with:
      $product_query = tep_db_query("select p.products_id, pd.products_name, p.products_price, s.specials_new_products_price, s.expires_date, s.valid_from_date from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_SPECIALS . " s where p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "' and p.products_id = s.products_id and s.specials_id = '" . (int)$HTTP_GET_VARS['sID'] . "'");



// new input field for valid from
replace (line 218)
          <tr>
            <td class="main"><?php echo TEXT_SPECIALS_EXPIRES_DATE; ?>&nbsp;</td>
            <td class="main"><?php echo tep_draw_input_field('day', (isset($sInfo->expires_date) ? substr($sInfo->expires_date, 8, 2) : ''), 'size="2" maxlength="2" class="cal-TextBox"') . tep_draw_input_field('month', (isset($sInfo->expires_date) ? substr($sInfo->expires_date, 5, 2) : ''), 'size="2" maxlength="2" class="cal-TextBox"') . tep_draw_input_field('year', (isset($sInfo->expires_date) ? substr($sInfo->expires_date, 0, 4) : ''), 'size="4" maxlength="4" class="cal-TextBox"'); ?><a class="so-BtnLink" href="javascript:calClick();return false;" onmouseover="calSwapImg('BTN_date', 'img_Date_OVER',true);" onmouseout="calSwapImg('BTN_date', 'img_Date_UP',true);" onclick="calSwapImg('BTN_date', 'img_Date_DOWN');showCalendar('new_special','dteWhen','BTN_date');return false;"><?php echo tep_image(DIR_WS_IMAGES . 'cal_date_up.gif', 'Calendar', '22', '17', 'align="absmiddle" name="BTN_date"'); ?></a></td>
          </tr>

with:
          <tr>
            <td class="main"><?php echo TEXT_SPECIALS_VALIDFROM_DATE; ?>&nbsp;</td>
            <td class="main"><?php echo tep_draw_input_field('vday', (isset($sInfo->valid_from_date) ? substr($sInfo->valid_from_date, 8, 2) : ''), 'size="2" maxlength="2" class="cal-TextBox"') . tep_draw_input_field('vmonth', (isset($sInfo->valid_from_date) ? substr($sInfo->valid_from_date, 5, 2) : ''), 'size="2" maxlength="2" class="cal-TextBox"') . tep_draw_input_field('vyear', (isset($sInfo->valid_from_date) ? substr($sInfo->valid_from_date, 0, 4) : ''), 'size="4" maxlength="4" class="cal-TextBox"'); ?><a class="so-BtnLink" href="javascript:calClick();return false;" onmouseover="calSwapImg('BTN_valid_date', 'img_Date_OVER',true);" onmouseout="calSwapImg('BTN_valid_date', 'img_Date_UP',true);" onclick="calSwapImg('BTN_valid_date', 'img_Date_DOWN');showCalendar('new_special','vday', 'vmonth', 'vyear','dte_valid_When','BTN_valid_date');return false;"><?php echo tep_image(DIR_WS_IMAGES . 'cal_date_up.gif', 'Calendar', '22', '17', 'align="absmiddle" name="BTN_valid_date"'); ?></a>
          </tr>
          <tr>
            <td class="main"><?php echo TEXT_SPECIALS_EXPIRES_DATE; ?>&nbsp;</td>
            <td class="main"><?php echo tep_draw_input_field('day', (isset($sInfo->expires_date) ? substr($sInfo->expires_date, 8, 2) : ''), 'size="2" maxlength="2" class="cal-TextBox"') . tep_draw_input_field('month', (isset($sInfo->expires_date) ? substr($sInfo->expires_date, 5, 2) : ''), 'size="2" maxlength="2" class="cal-TextBox"') . tep_draw_input_field('year', (isset($sInfo->expires_date) ? substr($sInfo->expires_date, 0, 4) : ''), 'size="4" maxlength="4" class="cal-TextBox"'); ?><a class="so-BtnLink" href="javascript:calClick();return false;" onmouseover="calSwapImg('BTN_date', 'img_Date_OVER',true);" onmouseout="calSwapImg('BTN_date', 'img_Date_UP',true);" onclick="calSwapImg('BTN_date', 'img_Date_DOWN');showCalendar('new_special', 'day', 'month','year', 'dteWhen','BTN_date');return false;"><?php echo tep_image(DIR_WS_IMAGES . 'cal_date_up.gif', 'Calendar', '22', '17', 'align="absmiddle" name="BTN_date"'); ?></a></td>
          </tr>


// fetch the valid from date so it can be displayed in the overview
replace (line 250)
    $specials_query_raw = "select p.products_id, pd.products_name, p.products_price, s.specials_id, s.specials_new_products_price, s.specials_date_added, s.specials_last_modified, s.expires_date, s.date_status_change, s.status from " . TABLE_PRODUCTS . " p, " . TABLE_SPECIALS . " s, " . TABLE_PRODUCTS_DESCRIPTION . " pd where p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "' and p.products_id = s.products_id order by pd.products_name";
with:
    $specials_query_raw = "select p.products_id, pd.products_name, p.products_price, s.specials_id, s.specials_new_products_price, s.specials_date_added, s.specials_last_modified, s.expires_date, s.date_status_change, s.status, s.valid_from_date from " . TABLE_PRODUCTS . " p, " . TABLE_SPECIALS . " s, " . TABLE_PRODUCTS_DESCRIPTION . " pd where p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "' and p.products_id = s.products_id order by pd.products_name";


// show valid from in overviews
replace (line 325)
        $contents[] = array('text' => '<br>' . TEXT_INFO_EXPIRES_DATE . ' <b>' . tep_date_short($sInfo->expires_date) . '</b>');
with
        $contents[] = array('text' => '<br>' . TEXT_INFO_VALIDFROM_DATE . ' <b>' . tep_date_short($sInfo->valid_from_date) . '</b>');
        $contents[] = array('text' => '' . TEXT_INFO_EXPIRES_DATE . ' <b>' . tep_date_short($sInfo->expires_date) . '</b>');

