Merge pull request #11 from lbryio/invites

Invites
This commit is contained in:
Jeremy Kauffman 2016-07-04 17:18:52 -04:00 committed by GitHub
commit fb8c2c7afc
15 changed files with 334 additions and 172 deletions

View file

@ -76,7 +76,8 @@ class Controller
return static::redirect('https://github.com/lbryio/lbry/releases/download/v0.2.5/lbry_0.2.5_amd64.deb', 307); return static::redirect('https://github.com/lbryio/lbry/releases/download/v0.2.5/lbry_0.2.5_amd64.deb', 307);
case '/art': case '/art':
return static::redirect('/what'); return static::redirect('/what');
default: }
$newsPattern = '#^' . ContentActions::URL_NEWS . '(/|$)#'; $newsPattern = '#^' . ContentActions::URL_NEWS . '(/|$)#';
if (preg_match($newsPattern, $uri)) if (preg_match($newsPattern, $uri))
{ {
@ -87,6 +88,7 @@ class Controller
} }
return $slug ? ContentActions::executePost($uri) : ContentActions::executeNews(); return $slug ? ContentActions::executePost($uri) : ContentActions::executeNews();
} }
$faqPattern = '#^' . ContentActions::URL_FAQ . '(/|$)#'; $faqPattern = '#^' . ContentActions::URL_FAQ . '(/|$)#';
if (preg_match($faqPattern, $uri)) if (preg_match($faqPattern, $uri))
{ {
@ -94,6 +96,13 @@ class Controller
return $slug ? ContentActions::executePost($uri) : ContentActions::executeFaq(); return $slug ? ContentActions::executePost($uri) : ContentActions::executeFaq();
} }
$noSlashUri = ltrim($uri, '/'); $noSlashUri = ltrim($uri, '/');
$accessPattern = '#^/signup#';
if (preg_match($accessPattern, $uri))
{
return DownloadActions::executeSignup();
}
if (View::exists('page/' . $noSlashUri)) if (View::exists('page/' . $noSlashUri))
{ {
return ['page/' . $noSlashUri, []]; return ['page/' . $noSlashUri, []];
@ -103,7 +112,6 @@ class Controller
return ['page/404', [], [static::HEADER_STATUS => 404]]; return ['page/404', [], [static::HEADER_STATUS => 404]];
} }
} }
}
public static function redirect($url, $statusCode = 302) public static function redirect($url, $statusCode = 302)
{ {

View file

@ -8,6 +8,9 @@
class Session class Session
{ {
const KEY_MAILCHIMP_LIST_IDS = 'mailchimp_list_ids', const KEY_MAILCHIMP_LIST_IDS = 'mailchimp_list_ids',
KEY_DOWNLOAD_ACCESS_ERROR = 'download_error',
KEY_DOWNLOAD_REFERRAL_CODE = 'beta_referral_code',
KEY_DOWNLOAD_ALLOWED = 'beta_download_allowed',
KEY_LIST_SUB_ERROR = 'list_error', KEY_LIST_SUB_ERROR = 'list_error',
KEY_LIST_SUB_SIGNATURE = 'list_sub_sig', KEY_LIST_SUB_SIGNATURE = 'list_sub_sig',
KEY_LIST_SUB_SUCCESS = 'list_success', KEY_LIST_SUB_SUCCESS = 'list_success',

View file

@ -5,6 +5,9 @@
* *
* @author jeremy * @author jeremy
*/ */
class PrefinerySubscribeException extends Exception {}
class DownloadActions extends Actions class DownloadActions extends Actions
{ {
const OS_ANDROID = 'android', const OS_ANDROID = 'android',
@ -25,6 +28,20 @@ class DownloadActions extends Actions
} }
public static function executeGet() public static function executeGet()
{
if (Session::get(Session::KEY_DOWNLOAD_ALLOWED))
{
return static::executeGetAccepted();
}
$os = static::guessOs();
return ['download/get', [
'os' => $os
]];
}
public static function executeGetAccepted()
{ {
$osChoices = static::getOses(); $osChoices = static::getOses();
$os = static::guessOs(); $os = static::guessOs();
@ -32,21 +49,67 @@ class DownloadActions extends Actions
if ($os && isset($osChoices[$os])) if ($os && isset($osChoices[$os]))
{ {
list($uri, $osTitle, $osIcon, $partial) = $osChoices[$os]; list($uri, $osTitle, $osIcon, $partial) = $osChoices[$os];
return ['download/get', [ $inviteCode = Session::get(Session::KEY_DOWNLOAD_REFERRAL_CODE);
return ['download/getAllowed', [
'inviteCode' => $inviteCode,
'os' => $os, 'os' => $os,
'osTitle' => $osTitle, 'osTitle' => $osTitle,
'osIcon' => $osIcon, 'osIcon' => $osIcon,
'hasMatchingInvite' => isset($_POST['invite']) && preg_match('/^(pfa|pfb).*$/', $_POST['invite']),
'hasInvite' => isset($_POST['invite']) && $_POST['invite'],
'downloadHtml' => View::exists('download/' . $partial) ? 'downloadHtml' => View::exists('download/' . $partial) ?
View::render('download/' . $partial, ['downloadUrl' => static::getDownloadUrl($os)]) : View::render('download/' . $partial, ['downloadUrl' => static::getDownloadUrl($os)]) :
false false
]]; ]];
} }
return ['download/get-no-os', [ return ['download/get-no-os'];
'isSubscribed' => in_array(Mailchimp::LIST_GENERAL_ID, Session::get(Session::KEY_MAILCHIMP_LIST_IDS, [])) }
]];
public static function executeSignup()
{
$email = isset($_GET['email']) ? $_GET['email'] : (isset($_POST['email']) ? $_POST['email'] : null);
$code = isset($_GET['code']) ? $_GET['code'] : (isset($_POST['code']) ? $_POST['code'] : null);
if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL))
{
Session::set(Session::KEY_DOWNLOAD_ACCESS_ERROR, 'Please provide a valid email. You provided: ' . $email);
}
elseif ($code)
{
try
{
static::subscribeToPrefinery($email, $code);
Session::set(Session::KEY_DOWNLOAD_ALLOWED, true);
}
catch (PrefinerySubscribeException $e)
{
Session::set(Session::KEY_DOWNLOAD_ACCESS_ERROR, 'Your code is not valid. If you believe this is an error, please contact ' . Config::HELP_CONTACT_EMAIL);
}
}
else
{
$referrerId = isset($_GET['referrer_id']) ? $_GET['referrer_id'] : (isset($_POST['referrer_id']) ? $_POST['referrer_id'] : null);
$failure = false;
try
{
// MailActions::subscribeToMailchimp($email, Mailchimp::LIST_GENERAL_ID);
static::subscribeToPrefinery($email, null, $referrerId);
}
catch (PrefinerySubscribeException $e)
{
$failure = true;
}
catch (MailchimpSubscribeException $e)
{
$failure = true;
}
if ($failure)
{
Session::set(Session::KEY_DOWNLOAD_ACCESS_ERROR,
'We were unable to add you to the wait list due to a technical error. Please contact ' . Config::HELP_CONTACT_EMAIL . ' for bonus credits.');
}
}
return Controller::redirect('/get');
} }
public static function prepareListPartial(array $vars) public static function prepareListPartial(array $vars)
@ -57,6 +120,68 @@ class DownloadActions extends Actions
]; ];
} }
public static function subscribeToPrefinery($email, $inviteCode = null, $referrerId = null)
{
$apiKey = Config::get('prefinery_key');
$options = [
'headers' => [
'Accept: application/json',
'Content-type: application/json'
],
'json_post' => true
];
$listUrl = 'https://lbry.prefinery.com/api/v2/betas/8679/testers.json?api_key=' . $apiKey;
$body = Curl::get($listUrl, ['email' => $email], $options);
if (!$body)
{
throw new PrefinerySubscribeException('Empty cURL response.');
}
$data = json_decode($body, true);
if ($data && is_array($data) && count($data) && isset($data[0]['share_link']))
{
$shareLink = $data[0]['share_link'];
}
else
{
$createUrl = 'https://lbry.prefinery.com/api/v2/betas/8679/testers.json?api_key=' . $apiKey;
$params = [
'tester' => array_filter([
'email' => $email,
'status' => $inviteCode ? 'invited' : 'applied',
'invitation_code' => $inviteCode,
'referrer_id' => $referrerId
])
];
$body = Curl::post($createUrl, $params, $options);
if (!$body)
{
throw new PrefinerySubscribeException('Empty cURL response.');
}
$data = json_decode($body, true);
if (!$data || !isset($data['share_link']))
{
throw new PrefinerySubscribeException('Missing share_link.');
}
$shareLink = $data['share_link'];
}
preg_match('/\?r\=(\w+)/', $shareLink, $matches);
Session::set(Session::KEY_DOWNLOAD_REFERRAL_CODE, $matches[1] ?: 'unknown');
return true;
}
protected static function guessOs() protected static function guessOs()
{ {
//if exact OS is requested, use that //if exact OS is requested, use that
@ -138,40 +263,4 @@ class DownloadActions extends Actions
return null; return null;
} }
// protected static function validateDownloadAccess()
// {
// $seshionKey = 'has-get-access';
// if (Session::get($seshionKey))
// {
// return true;
// }
//
// if ($_SERVER['REQUEST_METHOD'] === 'POST')
// {
// $accessCodes = include ROOT_DIR . '/data/secret/access_list.php';
// $today = date('Y-m-d H:i:s');
// foreach($accessCodes as $code => $date)
// {
// if ($_POST['invite'] == $code && $today <= $date)
// {
// Session::set($seshionKey, true);
// Controller::redirect('/get', 302);
// }
// }
//
// if ($_POST['invite'])
// {
// Session::set('invite_error', 'Please provide a valid invite code.');
// }
// else
// {
// Session::set('invite_error', 'Please provide an invite code.');
// }
//
// Controller::redirect('/get', 401);
// }
//
// return false;
// }
} }

View file

@ -29,11 +29,11 @@ class MailActions extends Actions
} }
else else
{ {
$mcApi = new Mailchimp();
$mcListId = $_POST['listId']; $mcListId = $_POST['listId'];
$mergeFields = isset($_POST['mergeFields']) ? unserialize($_POST['mergeFields']) : []; $mergeFields = isset($_POST['mergeFields']) ? unserialize($_POST['mergeFields']) : [];
$success = $mcApi->listSubscribe($mcListId, $email, $mergeFields, 'html', false); $errorOrSuccess = static::subscribeToMailchimp($email, $mcListId, $mergeFields);
if ($success)
if ($errorOrSuccess === true)
{ {
Session::set(Session::KEY_MAILCHIMP_LIST_IDS, array_merge(Session::get(Session::KEY_MAILCHIMP_LIST_IDS, []), [$mcListId])); Session::set(Session::KEY_MAILCHIMP_LIST_IDS, array_merge(Session::get(Session::KEY_MAILCHIMP_LIST_IDS, []), [$mcListId]));
Session::set(Session::KEY_LIST_SUB_SUCCESS, true); Session::set(Session::KEY_LIST_SUB_SUCCESS, true);
@ -41,14 +41,24 @@ class MailActions extends Actions
} }
else else
{ {
$error = $mcApi->errorMessage ?: __('Something went wrong adding you to the list.'); Session::set(Session::KEY_LIST_SUB_ERROR, $errorOrSuccess);
Session::set(Session::KEY_LIST_SUB_ERROR, $error);
} }
} }
return Controller::redirect($nextUrl); return Controller::redirect($nextUrl);
} }
public static function subscribeToMailchimp($email, $listId, array $mergeFields = [])
{
$mcApi = new Mailchimp();
$success = $mcApi->listSubscribe($listId, $email, $mergeFields, 'html', false);
if (!$success)
{
throw new MailchimpSubscribeException($mcApi->errorMessage ?: __('Something went wrong adding you to the list.'));
}
return true;
}
public static function prepareJoinList(array $vars) public static function prepareJoinList(array $vars)
{ {
$vars['listSig'] = md5(serialize($vars)); $vars['listSig'] = md5(serialize($vars));

View file

@ -1,5 +1,7 @@
<?php <?php
class MailchimpSubscribeException extends Exception {}
class Mailchimp extends MCAPI class Mailchimp extends MCAPI
{ {
const LIST_GENERAL_ID = '7b74c90030'; const LIST_GENERAL_ID = '7b74c90030';

View file

@ -2,6 +2,8 @@
class Config class Config
{ {
const HELP_CONTACT_EMAIL = 'josh@lbry.io';
protected static $loaded = false; protected static $loaded = false;
protected static $data = []; protected static $data = [];

View file

@ -51,6 +51,11 @@ class Curl
$ch = curl_init(); $ch = curl_init();
if ($ch === false || $ch === null)
{
throw new LogicException('Unable to initialize cURL');
}
if ($method == static::GET && $params) if ($method == static::GET && $params)
{ {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params); $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params);
@ -112,7 +117,6 @@ class Curl
return strlen($h); return strlen($h);
}); });
$responseContent = curl_exec($ch); $responseContent = curl_exec($ch);
$statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);

View file

@ -1,5 +1,5 @@
<div class="notice notice-info spacer1"> <div class="notice notice-info spacer1">
<em>This is a pre-release, beta version of LBRY.</em> <strong>This is still a beta.</strong>
It may crash, work unreliably, or inadvertently put a curse on your family for generations (a common programming error). While LBRY is now live, it may crash, work unreliably, or inadvertently put a curse on your family for generations (a common programming error).
Use at your own risk. Use at your own risk.
</div> </div>

View file

@ -1,30 +0,0 @@
<?php $reward = CreditApi::getCurrentTestCreditReward() ?>
<h3>Test and Earn</h3>
<?php echo View::render('download/_feedbackPrompt') ?>
<ol>
<li>Open LBRY.</li>
<li>Search and stream <code>wonderfullife</code>. Continue to play as you desire.</li>
<li><a href="/feedback" class="btn btn-alt">Provide Your Feedback</a></li>
</ol>
<p>
In addition to <?php echo i18n::formatCredits($reward) ?>, your feedback will be personally read by the developers and help signal
interest in LBRY to investors.
</p>
<?php /*
<?php $reward = CreditApi::getCurrentTestCreditReward() ?>
<div class="cover cover-dark cover-dark-grad content content-dark">
<h1>Test and Earn</h1>
<?php echo View::render('download/feedback-prompt') ?>
<h3>Test Your Install</h3>
<ol>
<li>Double click the app (LBRY will open in your browser)</li>
<li>Search and stream <code>wonderfullife</code></li>
<li>Continue to play as you desire</li>
</ol>
<h3>Feedback</h3>
<p>
In addition to <?php echo i18n::formatCredits($reward) ?>, your feedback will be personally read by the developers and help signal
interest in LBRY to investors.
</p>
<a href="/feedback" class="btn-alt">Provide Your Feedback</a>
</div> */ ?>

View file

@ -1,15 +1,8 @@
<br/> <div class="notice notice-info">
<p>LBRY is coming out on your favorite platform soon. Join our list to know when.</p> <p>LBRY is not yet out on your platform. You will receive an email as we expand LBRY to your preferred platform.</p>
<div class="spacer2"> <?php if ($os == DownloadActions::OS_OSX): ?>
<?php echo View::render('mail/joinList', [ <p>Arrival is expected by July 5.</p>
'submitLabel' => 'Go', <?php elseif ($os == DownloadActions::OS_WINDOWS): ?>
'returnUrl' => '/get', <p>Arrival is expected this month.</p>
'meta' => true, <?php endif ?>
'btnClass' => 'btn-alt',
'listId' => Mailchimp::LIST_GENERAL_ID,
'mergeFields' => ['CLI' => 'No'],
]) ?>
</div> </div>
<p>Can't wait? View the source and compile instructions on
<a href="https://github.com/lbryio/lbry" class="link-primary">GitHub</a>.
</p>

View file

@ -1,20 +0,0 @@
<?php Response::setMetaDescription(__('Download/install the latest version of LBRY for %os%.', ['%os%' => $osTitle])) ?>
<?php NavActions::setNavUri('/get') ?>
<?php echo View::render('nav/header', ['isDark' => false]) ?>
<main class="column-fluid">
<div class="span7">
<div class="cover cover-dark cover-dark-grad content content-stretch content-dark">
<h1>LBRY for <?php echo $osTitle ?> <span class="<?php echo $osIcon ?>"></span></h1>
<p>LBRY is </p>
</div>
</div>
<div class="span5">
<?php echo View::render('download/_list', [
'excludeOs' => $os
]) ?>
<?php echo View::render('download/_social') ?>
</div>
</main>
<?php echo View::render('nav/footer') ?>

View file

@ -1,39 +1,87 @@
<?php Response::setMetaDescription(__('Download/install the latest version of LBRY for %os%.', ['%os%' => $osTitle])) ?> <?php Response::setMetaDescription(__('Download/install the latest version of LBRY.')) ?>
<?php NavActions::setNavUri('/get') ?> <?php NavActions::setNavUri('/get') ?>
<?php echo View::render('nav/header', ['isDark' => false]) ?> <?php echo View::render('nav/header', ['isDark' => false]) ?>
<main class="column-fluid"> <main class="column-fluid">
<div class="span7"> <div class="span7">
<div class="cover cover-dark cover-dark-grad content content-stretch content-dark"> <div class="cover cover-dark cover-dark-grad content content-stretch content-dark">
<h1>LBRY for <?php echo $osTitle ?> <span class="<?php echo $osIcon ?>"></span></h1> <h1>Get LBRY</h1>
<?php if (!$hasMatchingInvite && !Session::get(Session::KEY_LIST_SUB_SUCCESS)): ?> <?php if (Session::get(Session::KEY_DOWNLOAD_ACCESS_ERROR)): ?>
<?php if ($hasInvite): ?> <div class="notice notice-error spacer1"><?php echo Session::get(Session::KEY_DOWNLOAD_ACCESS_ERROR) ?></div>
<div class="notice notice-error spacer1">Please enter a valid code.</div> <?php Session::unsetKey(Session::KEY_DOWNLOAD_ACCESS_ERROR) ?>
<?php endif ?> <?php endif ?>
<p>LBRY is currently in invite only mode. Enter your code below for access:</p>
<form method="POST" action="/get"> <?php if (Session::get(Session::KEY_DOWNLOAD_REFERRAL_CODE)): ?>
<div class="invite-submit"> <div class="notice notice-info spacer1">
<input type="text" value="" name="invite" class="required standard" placeholder="abc123"> <p>You are currently on the wait list. Move up the list and earn <?php echo i18n::formatCredits(25) ?> per referral by sharing this URL:</p>
<p><a href="/get?r=<?php echo Session::get(Session::KEY_DOWNLOAD_REFERRAL_CODE) ?>">https://lbry.io/get?r=<?php echo Session::get(Session::KEY_DOWNLOAD_REFERRAL_CODE) ?></a></p>
</div>
<?php endif ?>
<p>LBRY is currently in invite only mode. Enter your email to join the waitlist, or your email and invite code for access.</p>
<form method="POST" action="/signup" id="signup-form" class="hide">
<div class="hide">
<input type="hidden" name="referrer_id" value="<?php echo $_GET['r'] ?: $_POST['r'] ?>" />
</div>
<div class="form-row">
<label for="email">
<?php echo __('Email') ?>
</label>
<div class="form-input">
<input type="text" value="" name="email" class="required standard" placeholder="someone@somewhere.com">
</div>
</div>
<div class="form-row">
<label for="code_select">
<?php echo __('Invite Code') ?>
</label>
<div class="form-input">
<label class="label-radio">
<input name="code_select" type="radio" value="" />
None, but I want in as soon as possible!
</label>
</div>
<div class="form-input">
<label class="label-radio">
<input name="code_select" type="radio" value="yes" />
Yes
</label>
</div>
<div class="form-input has-code">
<input type="text" value="" name="code" class="required standard" placeholder="abc123">
</div>
</div>
<div class="invite-submit has-code">
<input type="submit" value="Access LBRY" name="subscribe" class="btn-alt"> <input type="submit" value="Access LBRY" name="subscribe" class="btn-alt">
</div> </div>
<div class="invite-submit no-code">
<input type="submit" value="Join List" name="subscribe" class="btn-alt">
</div>
</form> </form>
<?php else: ?> <?php js_start() ?>
<p>Your code does not grant access until July 4th, 2016. Enter your email address below for a reminder.</p> (function(){
<?php echo View::render('mail/joinList', [ var form = $('#signup-form'),
'submitLabel' => 'Go', codeRadioInputs = form.find('input[name="code_select"]');
'returnUrl' => '/get', codeRadioInputs.change(function() {
'meta' => true, var selectedInput = codeRadioInputs.filter(':checked'),
'btnClass' => 'btn-alt', choice = selectedInput.val(),
'listId' => Mailchimp::LIST_GENERAL_ID, hasChoice = selectedInput.length,
'mergeFields' => ['CLI' => 'No'], hasCode = choice == 'yes';
]) ?>
<?php endif ?> form.find('.has-code')[hasChoice && hasCode ? 'show' : 'hide']();
form.find('.no-code')[hasChoice && !hasCode ? 'show' : 'hide']();
if (!hasCode)
{
form.find('input[name="code"]').val('');
}
}).change();
form.show();
})();
<?php js_end() ?>
</div> </div>
</div> </div>
<div class="span5"> <div class="span5">
<?php echo View::render('download/_list', [
'excludeOs' => $os
]) ?>
<?php echo View::render('download/_social') ?> <?php echo View::render('download/_social') ?>
</div> </div>
</main> </main>

View file

@ -0,0 +1,33 @@
<?php Response::setMetaDescription(__('Download/install the latest version of LBRY for %os%.', ['%os%' => $osTitle])) ?>
<?php NavActions::setNavUri('/get') ?>
<?php echo View::render('nav/header', ['isDark' => false]) ?>
<main class="column-fluid">
<div class="span7">
<div class="cover cover-dark cover-dark-grad content content-stretch content-dark">
<h1>LBRY for <?php echo $osTitle ?> <span class="<?php echo $osIcon ?>"></span></h1>
<?php if ($downloadHtml): ?>
<?php echo View::render('download/_betaNotice') ?>
<?php echo $downloadHtml ?>
<?php else: ?>
<?php echo View::render('download/_unavailable', [
'os' => $os
]) ?>
<?php endif ?>
<h3>Share and Earn</h3>
<p>Earn <?php echo i18n::formatCredits(25) ?> per referral by sharing this URL:</p>
<p><a href="/get?r=<?php echo Session::get(Session::KEY_DOWNLOAD_REFERRAL_CODE) ?>" class="link-primary">https://lbry.io/get?r=<?php echo Session::get(Session::KEY_DOWNLOAD_REFERRAL_CODE) ?></a></p>
<?php echo View::render('download/_reward', [
'inviteCode' => $inviteCode
]) ?>
</div>
</div>
<div class="span5">
<?php echo View::render('download/_list', [
'excludeOs' => $os
]) ?>
<?php echo View::render('download/_social') ?>
</div>
</main>
<?php echo View::render('nav/footer') ?>

View file

@ -2,7 +2,7 @@
$input-width: 300px; $input-width: 300px;
label[for] label[for], .label-radio
{ {
cursor: pointer; cursor: pointer;
} }
@ -35,6 +35,22 @@ textarea
.form-row .form-row
{ {
margin-bottom: $spacing-vertical; margin-bottom: $spacing-vertical;
> label
{
font-size: 0.8em;
opacity: 0.9;
font-weight: bold;
}
}
input[type="checkbox"]
{
-webkit-appearance: checkbox;
}
input[type="radio"]
{
-webkit-appearance: radio;
} }
.mail-submit, .invite-submit .mail-submit, .invite-submit

View file

@ -38,6 +38,10 @@ $info_text: #3a87ad;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
a[href] { text-decoration: underline; } a[href] { text-decoration: underline; }
@include border-radius(5px); @include border-radius(5px);
> p:last-child
{
margin-bottom: 0;
}
} }
.notice-success .notice-success
{ {