[Slashdotjp-dev 435] CVS update: slashjp/plugins/Daypass

Back to archive index

Tatsuki SUGIURA sugi****@users*****
2006年 7月 12日 (水) 20:41:47 JST


Index: slashjp/plugins/Daypass/Daypass.pm
diff -u /dev/null slashjp/plugins/Daypass/Daypass.pm:1.1
--- /dev/null	Wed Jul 12 20:41:47 2006
+++ slashjp/plugins/Daypass/Daypass.pm	Wed Jul 12 20:41:46 2006
@@ -0,0 +1,315 @@
+# This code is a part of Slash, and is released under the GPL.
+# Copyright 1997-2005 by Open Source Technology Group. See README
+# and COPYING for more information, or see http://slashcode.com/.
+# $Id: Daypass.pm,v 1.1 2006/07/12 11:41:46 sugi Exp $
+
+package Slash::Daypass;
+
+use strict;
+use Slash::Utility;
+use Slash::DB::Utility;
+use Apache::Cookie;
+use vars qw($VERSION);
+use base 'Slash::DB::Utility';
+
+($VERSION) = ' $Revision: 1.1 $ ' =~ /\$Revision:\s+([^\s]+)/;
+
+# FRY: And where would a giant nerd be? THE LIBRARY!
+
+#################################################################
+sub new {
+	my($class, $user) = @_;
+	my $self = {};
+
+	my $plugin = getCurrentStatic('plugin');
+	return unless $plugin->{Daypass};
+
+	bless($self, $class);
+	$self->{virtual_user} = $user;
+	$self->sqlConnect();
+
+	return $self;
+}
+
+#################################################################
+{ # closure
+my $_getDA_cache;
+my $_getDA_cached_nextcheck;
+sub getDaypassesAvailable {
+	my($self) = @_;
+	my $constants = getCurrentStatic();
+
+	if (!$_getDA_cache
+		|| !$_getDA_cached_nextcheck
+		|| $_getDA_cached_nextcheck <= time()) {
+
+		$_getDA_cached_nextcheck = time() + ($constants->{daypass_cache_expire} || 300);
+		if (!$constants->{daypass_offer_method}) {
+			# (Re)load the cache from a reader DB.
+			my $reader = getObject('Slash::DB', { db_type => 'reader' });
+			$_getDA_cache = $reader->sqlSelectAllHashrefArray(
+				"daid, adnum, minduration,
+				 UNIX_TIMESTAMP(starttime) AS startts, UNIX_TIMESTAMP(endtime) AS endts,
+				 aclreq",
+				"daypass_available");
+		} else {
+			my $pos = $constants->{daypass_offer_method1_adpos} || 31;
+			my $regex = $constants->{daypass_offer_method1_regex} || '!placeholder';
+			my $acl = $constants->{daypass_offer_method1_acl} || '';
+			my $minduration = $constants->{daypass_offer_method1_minduration} || 10;
+			my $avail = $self->checkAdposRegex($pos, $regex);
+			if ($avail) {
+				my $adnum = $constants->{daypass_adnum} || 13;
+				$_getDA_cache = [ {
+					daid =>		999, # dummy placeholder, not used
+					adnum =>	$adnum,
+					minduration =>	$minduration,
+					startts =>	time - 60,
+					endts =>	time + 3600,
+					aclreq =>	$acl,
+				} ];
+			} else {
+				$_getDA_cache = [ ];
+			}
+		}
+
+	}
+
+	return $_getDA_cache;
+}
+} # end closure
+
+sub checkAdposRegex {
+	my($self, $pos, $regex) = @_;
+	my $ad_text = getAd($pos);
+	return 0 if !$ad_text;
+	my $neg = 0;
+	if (substr($regex, 0, 1) eq '!') {
+		# Strip off leading char.
+		$neg = 1;
+		$regex = substr($regex, 1);
+	}
+	my $avail = ($ad_text =~ /$regex/) ? 1 : 0;
+	$avail = !$avail if $neg;
+	return $avail;
+}
+
+sub getDaypass {
+	my($self) = @_;
+
+	my $constants = getCurrentStatic();
+	return undef unless $constants->{daypass};
+
+	my $da_ar = $self->getDaypassesAvailable();
+	return undef if !$da_ar || !@$da_ar;
+
+	# There are one or more rows in the table, which might mean there
+	# are one or more daypass ads that we can show.
+	my @ads_available = ( );
+	my $time = time();
+	my $user = undef;
+	for my $hr (@$da_ar) {
+		next unless $hr->{startts} <= $time;
+		next unless $time <= $hr->{endts};
+		if ($constants->{daypass_offer_onlytologgedin}) {
+			$user ||= getCurrentUser();
+			next unless $user && !$user->{is_anon};
+		}
+		if ($hr->{aclreq}) {
+			$user ||= getCurrentUser();
+			print STDERR scalar(localtime) . " $$ cannot get user in getDaypass\n" if !$user;
+			next unless $user && !$user->{is_anon}
+				&& $user->{acl}{ $hr->{aclreq} };
+		}
+		push @ads_available, $hr;
+	}
+
+	return undef unless @ads_available;
+
+	# Return a random one.
+	return $ads_available[rand(@ads_available)];
+}
+
+sub createDaypasskey {
+	my($self, $dp_hr) = @_;
+
+	# If no daypass was available, we can't return a key.
+	return "" if !$dp_hr;
+
+	# How far in the future before this daypass can be confirmed?
+	# I.e. how much of the ad do we insist the user watch?
+	my $secs_ahead = $dp_hr->{minduration} || 0;
+	# Give the user a break of 1 second, to allow for clock drift
+	# or what-have-you.
+	$secs_ahead -= 1;
+
+	my $key = getAnonId(1, 20);
+	my $rows = $self->sqlInsert('daypass_keys', {
+		daypasskey		=> $key,
+		-key_given		=> 'NOW()',
+		-earliest_confirmable	=> "DATE_ADD(NOW(), INTERVAL $secs_ahead SECOND)",
+		key_confirmed		=> undef,
+	});
+	if ($rows < 1) {
+		return "";
+	} else {
+		return $key;
+	}
+}
+
+sub confirmDaypasskey {
+	my($self, $key) = @_;
+	my $constants = getCurrentStatic();
+
+	my $key_q = $self->sqlQuote($key);
+
+	my $rows = $self->sqlUpdate(
+		"daypass_keys",
+		{ -key_confirmed => "NOW()" },
+		"daypasskey = $key_q
+		 AND earliest_confirmable <= NOW()
+		 AND key_confirmed IS NULL");
+
+	my $confcode = "";
+	if ($rows > 0) {
+		$confcode = getAnonId(1, 20);
+		my $hr = {
+			confcode =>	$confcode,
+			gooduntil =>	$self->getGoodUntil(),
+		};
+		$rows = $self->sqlInsert('daypass_confcodes', $hr);
+	}
+
+	$rows = 0 if $rows < 1;
+	return $rows ? $confcode : 0;
+}
+
+sub getDaypassTZOffset {
+	my($self) = @_;
+	my $slashdb = getCurrentDB();
+	my $constants = getCurrentStatic();
+
+	my $dptz = $constants->{daypass_tz} || 'GMT';
+	return 0 if $dptz eq 'GMT';
+
+	my $timezones = $slashdb->getTZCodes();
+	return 0 unless $timezones && $timezones->{$dptz};
+	return $timezones->{$dptz}{off_set} || 0;
+}
+
+sub getGoodUntil {
+	my($self) = @_;
+	my $slashdb = getCurrentDB();
+
+	# The business decision made here is that all daypasses expire
+	# at the same time, midnight in some timezone.  This seems to
+	# make more sense than having different users' daypasses expire
+	# at different times.  I'm not really happy about putting
+	# business logic in this .pm file but I doubt this decision
+	# will change.  But if it does, here's the line of code that
+	# needs to change.
+	my $off_set = $self->getDaypassTZOffset() || 0;
+
+	# Determine the final second on the day for the timezone in
+	# question, expressed in GMT time.  If the timezone is EST, for
+	# which the offset is -18000 seconds, we determine this by bumping
+	# the current GMT datetime -18000 seconds, taking the GMT date,
+	# appending the time 23:59:59 to it, and re-adding +18000 to
+	# that time.
+	my $gmt_end_of_tz_day =
+		$off_set
+		? $slashdb->sqlSelect("DATE_SUB(
+				CONCAT(
+					SUBSTRING(
+						DATE_ADD( NOW(), INTERVAL $off_set SECOND ),
+						1, 10
+					),
+					' 23:59:59'
+				),
+			INTERVAL $off_set SECOND)")
+		: $slashdb->sqlSelect("CONCAT(SUBSTRING(NOW(), 1, 10), ' 23:59:59')");
+	# If there was an error of some kind, note it and at least
+	# return a legal value.
+	if (!$gmt_end_of_tz_day) {
+		errorLog("empty gmt_end_of_tz_day '$off_set' " . time);
+		$gmt_end_of_tz_day = '0000-00-00 00:00:00';
+	}
+	return $gmt_end_of_tz_day;
+}
+
+sub userHasDaypass {
+	my($self, $user) = @_;
+	my $form = getCurrentForm();
+	return 0 unless $ENV{GATEWAY_INTERFACE};
+	my $cookies = Apache::Cookie->fetch;
+	return 0 unless $cookies && $cookies->{daypassconfcode};
+	my $confcode = $cookies->{daypassconfcode}->value();
+	my $confcode_q = $self->sqlQuote($confcode);
+
+	# We really should memcached this.  But the query cache
+	# will take a lot of the sting out of it.
+	my $gooduntil = $self->sqlSelect(
+		'UNIX_TIMESTAMP(gooduntil)',
+		'daypass_confcodes',
+		"confcode=$confcode_q");
+	# If it's expired, it's no good.
+	$gooduntil = 0 if $gooduntil && $gooduntil < time();
+	return $gooduntil ? 1 : 0;
+}
+
+sub doOfferDaypass {
+	my($self) = @_;
+	# If daypasses are entirely turned off, or the var indicating whether
+	# to offer daypasses is set to false, then no.
+	my $constants = getCurrentStatic();
+	return 0 unless $constants->{daypass} && $constants->{daypass_offer};
+	# If the user is a subscriber, then no.
+	my $user = getCurrentUser();
+	return 0 if $user->{is_subscriber};
+	# If the user already has a daypass, then no.
+	return 0 if $self->userHasDaypass($user);
+	# If there are no ads available for this user, then no.
+	my $dp_hr = $self->getDaypass();
+	return 0 unless $dp_hr;
+	# Otherwise, yes. Return its daid.
+	return $dp_hr->{daid};
+}
+
+sub getOfferText {
+	my($self) = @_;
+	my $constants = getCurrentStatic();
+	my $text = "";
+	if (!$constants->{daypass_offer_method}) {
+		$text = Slash::getData('offertext', {}, 'daypass');
+	} else {
+		my $pos = $constants->{daypass_offer_method1_adpos} || 31;
+		$text = getAd($pos);
+	}
+	return $text;
+}
+
+#################################################################
+sub DESTROY {
+	my($self) = @_;
+	$self->{_dbh}->disconnect if $self->{_dbh} && !$ENV{GATEWAY_INTERFACE};
+}
+
+1;
+
+=head1 NAME
+
+Slash::Daypass - Slash Daypass module
+
+=head1 SYNOPSIS
+
+	use Slash::Daypass;
+
+=head1 DESCRIPTION
+
+This contains all of the routines currently used by Daypass.
+
+=head1 SEE ALSO
+
+Slash(3).
+
+=cut
Index: slashjp/plugins/Daypass/Makefile.PL
diff -u /dev/null slashjp/plugins/Daypass/Makefile.PL:1.1
--- /dev/null	Wed Jul 12 20:41:47 2006
+++ slashjp/plugins/Daypass/Makefile.PL	Wed Jul 12 20:41:46 2006
@@ -0,0 +1,8 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+    'NAME'	=> 'Slash::Daypass',
+    'VERSION_FROM' => 'Daypass.pm', # finds $VERSION
+    'PM'	=> { 'Daypass.pm' => '$(INST_LIBDIR)/Daypass.pm' },
+);
Index: slashjp/plugins/Daypass/PLUGIN
diff -u /dev/null slashjp/plugins/Daypass/PLUGIN:1.1
--- /dev/null	Wed Jul 12 20:41:47 2006
+++ slashjp/plugins/Daypass/PLUGIN	Wed Jul 12 20:41:46 2006
@@ -0,0 +1,9 @@
+# $Id: PLUGIN,v 1.1 2006/07/12 11:41:46 sugi Exp $
+name=Daypass
+description="Daypass"
+htdoc=daypass.pl
+mysql_dump=mysql_dump.sql
+mysql_schema=mysql_schema.sql
+template=templates/data;daypass;default
+template=templates/main;daypass;default
+
Index: slashjp/plugins/Daypass/daypass.pl
diff -u /dev/null slashjp/plugins/Daypass/daypass.pl:1.1
--- /dev/null	Wed Jul 12 20:41:47 2006
+++ slashjp/plugins/Daypass/daypass.pl	Wed Jul 12 20:41:46 2006
@@ -0,0 +1,112 @@
+#!/usr/bin/perl -w
+# This code is a part of Slash, and is released under the GPL.
+# Copyright 1997-2005 by Open Source Technology Group. See README
+# and COPYING for more information, or see http://slashcode.com/.
+# $Id: daypass.pl,v 1.1 2006/07/12 11:41:46 sugi Exp $
+
+use strict;
+use Slash;
+use Slash::Display;
+use Slash::Utility;
+
+##################################################################
+sub main {
+	my $gSkin = getCurrentSkin();
+	my $daypass_reader = getObject('Slash::Daypass', { db_type => 'reader' });
+	my $dps = $daypass_reader->getDaypassesAvailable();
+	if (!$dps || !@$dps) {
+		redirect($gSkin->{rootdir});
+	}
+
+	my $daypass_writer = getObject('Slash::Daypass');
+	my $form = getCurrentForm();
+	my $dpk = $form->{dpk} || "";
+	if ($dpk) {
+		# Strip this form field.
+		$dpk =~ /^(\w+)$/;
+		$dpk = $1 || "";
+	}
+
+	my($adnum, $minduration) = (0, 0);
+
+	if ($dpk) {
+
+		my $confcode = $daypass_writer->confirmDaypasskey($dpk);
+		if ($confcode) {
+			# Do the housekeeping required to echo the
+			# conf code out to the client's browser,
+			# and then don't continue with the rest of
+			# this function (in particular, don't create a
+			# new key).
+			key_confirmed($confcode);
+			return ;
+		}
+		# The user probably didn't watch enough of the
+		# ad.  Let them keep watching!
+		print STDERR scalar(localtime) . " daypass.pl $$ apparently early click\n";
+		$adnum = $form->{adnum};
+		$adnum =~ /^(\d+)$/;
+		$adnum = $1 || 0;
+		if (!$adnum) {
+			# We don't know which ad they were watching (they
+			# probably edited the URL) so fetch a new one.
+print STDERR scalar(localtime) . " daypass.pl $$ no adnum found, refetching\n";
+			$dpk = "";
+		}
+
+	}
+
+	if (!$dpk) {
+
+		my $dp_hr = $daypass_reader->getDaypass();
+		if (!$dp_hr) {
+			# Something went wrong.  We don't have a daypass for
+			# the user to see.
+print STDERR scalar(localtime) . " daypass.pl $$ cannot choose daypass\n";
+			redirect($gSkin->{rootdir});
+			return ;
+		}
+		$dpk = $daypass_writer->createDaypasskey($dp_hr);
+		if (!$dpk) {
+			# Something went wrong.  We can't show the user a key.
+print STDERR scalar(localtime) . " daypass.pl $$ cannot show key\n";
+			redirect($gSkin->{rootdir});
+			return ;
+		}
+		$adnum = $dp_hr->{adnum};
+		$minduration = $dp_hr->{minduration} || 0;
+
+	}
+
+	# Whether because the user just got a new key created for
+	# them, or because they clicked too fast and we're reusing
+	# their old key, they have a key in $dpk.
+
+	header(getData('head')) or return;
+
+	slashDisplay('main', {
+		adnum		=> $adnum,
+		dpk		=> $dpk,
+		minduration	=> $minduration,
+	});
+
+	footer();
+}
+
+sub key_confirmed {
+	my($confcode) = @_;
+	# Pause to allow replication to catch up, so when
+	# the user gets back to the homepage, they will
+	# show up as having the daypass.
+	sleep 2;
+	setCookie('daypassconfcode', $confcode, '+24h');
+	my $gSkin = getCurrentSkin();
+	redirect($gSkin->{rootdir});
+}
+
+#################################################################
+createEnvironment();
+main();
+
+1;
+
Index: slashjp/plugins/Daypass/mysql_dump.sql
diff -u /dev/null slashjp/plugins/Daypass/mysql_dump.sql:1.1
--- /dev/null	Wed Jul 12 20:41:47 2006
+++ slashjp/plugins/Daypass/mysql_dump.sql	Wed Jul 12 20:41:46 2006
@@ -0,0 +1,17 @@
+INSERT INTO hooks (param, class, subroutine) VALUES ('daypass_dooffer', 'Slash::Daypass', 'doOfferDaypass');
+INSERT INTO hooks (param, class, subroutine) VALUES ('daypass_getoffertext', 'Slash::Daypass', 'getOfferText');
+
+INSERT INTO vars (name, value, description) VALUES ('daypass', '0', 'Activate daypass system?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_adnum', '13', 'Which ad number to pass to getAd?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_cache_expire', '60', 'How long is the cache of the daypass_available table stored?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer', '0', 'Offer daypasses to logged-in non-subscriber users on the homepage?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_onlywhentmf', '0', 'Offer daypasses only when there is a story in The Mysterious Future?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_method', '0', 'How to determine whether a daypass is offered: 0=use daypass_available table, 1=check adpos text against regex');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_method1_acl', '', 'ACL required to be offered a daypass (blank for none, i.e. all users eligible)');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_method1_adpos', '31', 'If daypass_offer_method is 1, which ad position to check?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_method1_minduration', '10', 'Minimum time allowed before click');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_method1_regex', '!placeholder', 'If daypass_offer_method is 1, what regex on that ad text tells us whether a daypass is available? A leading ! inverts logic (regex match means daypass not available)');
+INSERT INTO vars (name, value, description) VALUES ('daypass_offer_onlytologgedin', '0', 'If 1, offer a daypass only to logged-in users');
+INSERT INTO vars (name, value, description) VALUES ('daypass_seetmf', '0', 'Should users with daypasses be able to, like subscribers, see The Mysterious Future?');
+INSERT INTO vars (name, value, description) VALUES ('daypass_tz', 'PST', 'What timezone are daypasses considered to be in (this determines where "midnight" starts and ends the day)');
+
Index: slashjp/plugins/Daypass/mysql_schema.sql
diff -u /dev/null slashjp/plugins/Daypass/mysql_schema.sql:1.1
--- /dev/null	Wed Jul 12 20:41:47 2006
+++ slashjp/plugins/Daypass/mysql_schema.sql	Wed Jul 12 20:41:46 2006
@@ -0,0 +1,62 @@
+# Create a row in this table to indicate which daypass is available
+# when.  Times are in GMT.
+
+CREATE TABLE daypass_available (
+	daid		SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
+	adnum		SMALLINT NOT NULL DEFAULT 0,
+	minduration	SMALLINT NOT NULL DEFAULT 0,
+	starttime	DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
+	endtime		DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
+	aclreq		VARCHAR(32) DEFAULT NULL,
+	PRIMARY KEY daid (daid)
+) TYPE=InnoDB;
+
+# Creating rows in this table can mark certain skins or stories as
+# requiring a daypass (or subscription) to read.  Using this is
+# optional (and currently there is no UI for admins to edit this
+# data).  To mark a story as daypass/subscription only, add a row
+# of type='article', data='[story sid]'.  To mark a skin such that
+# only daypass and subscriber users can view its index page or any
+# articles with that primaryskid, add a row of type='skin',
+# data='[skid]'.  To mark the entire site as requiring a daypass
+# to read, set type='site', and data does not matter.
+# The restrictions will be enforced from the starttime to the endtime,
+# or if endtime is NULL, from the starttime on.
+# If this table is empty, daypasses will be optional (and actually
+# that's all that is supported in the code right now).
+
+CREATE TABLE daypass_needs (
+	type		ENUM('skin', 'site', 'article') NOT NULL DEFAULT 'skin',
+	data		VARCHAR(255) NOT NULL,
+	starttime	DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
+	endtime		DATETIME DEFAULT NULL
+) TYPE=InnoDB;
+
+# Here is where daypass keys are temporarily stored, while users are
+# looking at the daypass page(s).  Once they have confirmed their key
+# by completing the daypass page viewing, key_confirmed is set to
+# non-NULL and a row for that user is created in daypass_users.
+# Times are in GMT.
+
+CREATE TABLE daypass_keys (
+	dpkid			INT UNSIGNED NOT NULL AUTO_INCREMENT,
+	daypasskey		CHAR(20) NOT NULL DEFAULT '',
+	daid			SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+	key_given		DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
+	earliest_confirmable	DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
+	key_confirmed		DATETIME DEFAULT NULL,
+	PRIMARY KEY dpkid (dpkid),
+	UNIQUE daypasskey (daypasskey),
+	KEY key_given (key_given)
+) TYPE=InnoDB;
+
+# Any user with a 'daypass_confcode' cookie in this table where the
+# confcode >= NOW() is considered to have a daypass.  It does not
+# matter whether the user is logged-in.  The time is in GMT.
+
+CREATE TABLE daypass_confcodes (
+	confcode	CHAR(20) NOT NULL DEFAULT '',
+	gooduntil	DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
+	PRIMARY KEY confcode (confcode)
+) TYPE=InnoDB;
+


Slashdotjp-dev メーリングリストの案内
Back to archive index