Merge remote-tracking branch 'gitorious/1.0.x' into 1.0.x

Conflicts:
	plugins/EmailRegistration/emailregister.php
This commit is contained in:
Evan Prodromou 2011-04-18 20:19:25 -04:00
commit 138ca38b6f
18 changed files with 689 additions and 121 deletions

View File

@ -291,11 +291,11 @@ class InviteAction extends CurrentUserDesignAction
// TRANS: Subject for invitation email. Note that 'them' is correct as a gender-neutral // TRANS: Subject for invitation email. Note that 'them' is correct as a gender-neutral
// TRANS: singular 3rd-person pronoun in English. %1$s is the inviting user, $2$s is // TRANS: singular 3rd-person pronoun in English. %1$s is the inviting user, $2$s is
// TRANS: the StatusNet sitename. // TRANS: the StatusNet sitename.
$headers['Subject'] = sprintf(_('%1$s has invited you to join them on %2$s'), $bestname, $sitename); $headers['Subject'] = sprintf(_('%1$s has invited you to join them on %2$s'), $bestname, $sitename);
$title = (empty($personal)) ? 'invite' : 'invitepersonal'; $title = (empty($personal)) ? 'invite' : 'invitepersonal';
// @todo FIXME: i18n issue.
$inviteTemplate = DocFile::forTitle($title, DocFile::mailPaths()); $inviteTemplate = DocFile::forTitle($title, DocFile::mailPaths());
$body = $inviteTemplate->toHTML(array('inviter' => $bestname, $body = $inviteTemplate->toHTML(array('inviter' => $bestname,

View File

@ -262,7 +262,7 @@ var SN = { // StatusNet
errorReported = $('#error', xhr.responseXML).text(); errorReported = $('#error', xhr.responseXML).text();
} }
alert(errorReported || errorThrown || textStatus); alert(errorReported || errorThrown || textStatus);
// Restore the form to original state. // Restore the form to original state.
// Hopefully. :D // Hopefully. :D
form form
@ -506,7 +506,7 @@ var SN = { // StatusNet
results_placeholder.replaceWith(list); results_placeholder.replaceWith(list);
} }
else { else {
var _error = $('<li/>').append(document._importNode($('p', data)[0], true)); var _error = $('<li/>').append(document._importNode($('p', data)[0], true));
results_placeholder.html(_error); results_placeholder.html(_error);
} }
form form
@ -650,7 +650,6 @@ var SN = { // StatusNet
// and we'll add on the end of it. Will add if needed. // and we'll add on the end of it. Will add if needed.
list = $('ul.threaded-replies', notice); list = $('ul.threaded-replies', notice);
if (list.length == 0) { if (list.length == 0) {
console.log("list = 0");
SN.U.NoticeInlineReplyPlaceholder(notice); SN.U.NoticeInlineReplyPlaceholder(notice);
list = $('ul.threaded-replies', notice); list = $('ul.threaded-replies', notice);
} else { } else {
@ -777,6 +776,7 @@ var SN = { // StatusNet
* popout before submitting. * popout before submitting.
* *
* Uses 'live' rather than 'bind', so applies to future as well as present items. * Uses 'live' rather than 'bind', so applies to future as well as present items.
*
*/ */
NoticeRepeat: function() { NoticeRepeat: function() {
$('.form_repeat').live('click', function(e) { $('.form_repeat').live('click', function(e) {

2
js/util.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -58,11 +58,16 @@ class DomainWhitelistPlugin extends Plugin
if (!$this->matchesWhitelist($email)) { if (!$this->matchesWhitelist($email)) {
$whitelist = $this->getWhitelist(); $whitelist = $this->getWhitelist();
if (count($whitelist) == 1) { if (count($whitelist) == 1) {
$message = sprintf(_("Email address must be in this domain: %s"), // TRANS: Client exception thrown when a given e-mailaddress is not in the domain whitelist.
// TRANS: %s is a whitelisted e-mail domain.
$message = sprintf(_m('Email address must be in this domain: %s.'),
$whitelist[0]); $whitelist[0]);
} else { } else {
$message = sprintf(_("Email address must be in one of these domains: %s"), // TRANS: Client exception thrown when a given e-mailaddress is not in the domain whitelist.
implode(', ', $whitelist)); // TRANS: %s are whitelisted e-mail domains separated by comma's (localisable).
$message = sprintf(_('Email address must be in one of these domains: %s.'),
// TRANS: Separator for whitelisted domains.
implode(_m('SEPARATOR',', '), $whitelist));
} }
throw new ClientException($message); throw new ClientException($message);
} }

View File

@ -45,7 +45,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class EmailRegistrationPlugin extends Plugin class EmailRegistrationPlugin extends Plugin
{ {
function onAutoload($cls) function onAutoload($cls)
@ -88,6 +87,7 @@ class EmailRegistrationPlugin extends Plugin
{ {
$dir = dirname(__FILE__); $dir = dirname(__FILE__);
// @todo FIXME: i18n issue.
$docFile = DocFile::forTitle($title, $dir.'/doc-src/'); $docFile = DocFile::forTitle($title, $dir.'/doc-src/');
if (!empty($docFile)) { if (!empty($docFile)) {
@ -105,7 +105,8 @@ class EmailRegistrationPlugin extends Plugin
'author' => 'Evan Prodromou', 'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:EmailRegistration', 'homepage' => 'http://status.net/wiki/Plugin:EmailRegistration',
'rawdescription' => 'rawdescription' =>
_m('Use email only for registration')); // TRANS: Plugin description.
_m('Use email only for registration.'));
return true; return true;
} }
} }

View File

@ -52,7 +52,6 @@ require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
* *
* @see DB_DataObject * @see DB_DataObject
*/ */
class User_greeting_count extends Memcached_DataObject class User_greeting_count extends Memcached_DataObject
{ {
public $__table = 'user_greeting_count'; // table name public $__table = 'user_greeting_count'; // table name
@ -174,7 +173,7 @@ class User_greeting_count extends Memcached_DataObject
if (!$result) { if (!$result) {
// TRANS: Exception thrown when the user greeting count could not be saved in the database. // TRANS: Exception thrown when the user greeting count could not be saved in the database.
// TRANS: %d is a user ID (number). // TRANS: %d is a user ID (number).
throw Exception(sprintf(_m("Could not increment greeting count for %d."), throw Exception(sprintf(_m('Could not increment greeting count for %d.'),
$user_id)); $user_id));
} }
} }

View File

@ -4,7 +4,7 @@
* Copyright (C) 2011, StatusNet, Inc. * Copyright (C) 2011, StatusNet, Inc.
* *
* Registration confirmation form * Registration confirmation form
* *
* PHP version 5 * PHP version 5
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -44,7 +44,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class ConfirmRegistrationForm extends Form class ConfirmRegistrationForm extends Form
{ {
protected $code; protected $code;
@ -62,15 +61,16 @@ class ConfirmRegistrationForm extends Form
function formData() function formData()
{ {
$this->out->element('p', 'instructions', $this->out->element('p', 'instructions',
sprintf(_('Enter a password to confirm your new account.'))); // TRANS: Form instructions.
sprintf(_m('Enter a password to confirm your new account.')));
$this->hidden('code', $this->code); $this->hidden('code', $this->code);
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->elementStart('li'); $this->elementStart('li');
$this->element('label', array('for' => 'nickname-ignore'), _('User name')); $this->element('label', array('for' => 'nickname-ignore'), _m('User name'));
$this->element('input', array('name' => 'nickname-ignore', $this->element('input', array('name' => 'nickname-ignore',
'type' => 'text', 'type' => 'text',
@ -82,7 +82,8 @@ class ConfirmRegistrationForm extends Form
$this->elementStart('li'); $this->elementStart('li');
$this->element('label', array('for' => 'email-ignore'), _('Email')); // TRANS: Field label.
$this->element('label', array('for' => 'email-ignore'), _m('Email address'));
$this->element('input', array('name' => 'email-ignore', $this->element('input', array('name' => 'email-ignore',
'type' => 'text', 'type' => 'text',
@ -94,15 +95,15 @@ class ConfirmRegistrationForm extends Form
$this->elementStart('li'); $this->elementStart('li');
// TRANS: Field label on account registration page. // TRANS: Field label on account registration page.
$this->password('password1', _('Password'), $this->password('password1', _m('Password'),
// TRANS: Field title on account registration page. // TRANS: Field title on account registration page.
_('6 or more characters.')); _m('6 or more characters.'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementStart('li'); $this->elementStart('li');
// TRANS: Field label on account registration page. In this field the password has to be entered a second time. // TRANS: Field label on account registration page. In this field the password has to be entered a second time.
$this->password('password2', _m('PASSWORD','Confirm'), $this->password('password2', _m('PASSWORD','Confirm'),
// TRANS: Field title on account registration page. // TRANS: Field title on account registration page.
_('Same as password above.')); _m('Same as password above.'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementStart('li'); $this->elementStart('li');
@ -117,12 +118,12 @@ class ConfirmRegistrationForm extends Form
$this->elementStart('label', array('class' => 'checkbox', $this->elementStart('label', array('class' => 'checkbox',
'for' => 'tos')); 'for' => 'tos'));
// TRANS: Checkbox title for terms of service and privacy policy.
$this->raw(sprintf(_('I agree to the <a href="%1$s">Terms of service</a> and '. $this->raw(sprintf(_m('I agree to the <a href="%1$s">Terms of service</a> and '.
'<a href="%1$s">Privacy policy</a> of this site.'), '<a href="%1$s">Privacy policy</a> of this site.'),
common_local_url('doc', array('title' => 'tos')), common_local_url('doc', array('title' => 'tos')),
common_local_url('doc', array('title' => 'privacy')))); common_local_url('doc', array('title' => 'privacy'))));
$this->elementEnd('label'); $this->elementEnd('label');
$this->elementEnd('li'); $this->elementEnd('li');
@ -146,7 +147,7 @@ class ConfirmRegistrationForm extends Form
function formActions() function formActions()
{ {
// TRANS: Button text for action to save a new bookmark. // TRANS: Button text for action to register.
$this->out->submit('submit', _m('BUTTON', 'Register')); $this->out->submit('submit', _m('BUTTON', 'Register'));
} }

View File

@ -4,7 +4,7 @@
* Copyright (C) 2011, StatusNet, Inc. * Copyright (C) 2011, StatusNet, Inc.
* *
* Register a user by their email address * Register a user by their email address
* *
* PHP version 5 * PHP version 5
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -39,10 +39,10 @@ if (!defined('STATUSNET')) {
* *
* There are four cases where we're called: * There are four cases where we're called:
* *
* 1. GET, no arguments. Initial registration; ask for an email address. * 1. GET, no arguments. Initial registration; ask for an email address.
* 2. POST, email address argument. Initial registration; send an email to confirm. * 2. POST, email address argument. Initial registration; send an email to confirm.
* 3. GET, code argument. Confirming an invitation or a registration; look them up, * 3. GET, code argument. Confirming an invitation or a registration; look them up,
* create the relevant user if possible, login as that user, and * create the relevant user if possible, login as that user, and
* show a password-entry form. * show a password-entry form.
* 4. POST, password argument. After confirmation, set the password for the new * 4. POST, password argument. After confirmation, set the password for the new
* user, and redirect to a registration complete action with some instructions. * user, and redirect to a registration complete action with some instructions.
@ -54,7 +54,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class EmailregisterAction extends Action class EmailregisterAction extends Action
{ {
const NEWEMAIL = 1; const NEWEMAIL = 1;
@ -95,7 +94,8 @@ class EmailregisterAction extends Action
$this->code = $this->trimmed('code'); $this->code = $this->trimmed('code');
if (empty($this->code)) { if (empty($this->code)) {
throw new ClientException(_('No confirmation code.')); // TRANS: Client exception thrown when no confirmation code was provided.
throw new ClientException(_m('No confirmation code.'));
} }
$this->invitation = Invitation::staticGet('code', $this->code); $this->invitation = Invitation::staticGet('code', $this->code);
@ -105,13 +105,14 @@ class EmailregisterAction extends Action
$this->confirmation = Confirm_address::staticGet('code', $this->code); $this->confirmation = Confirm_address::staticGet('code', $this->code);
if (empty($this->confirmation)) { if (empty($this->confirmation)) {
throw new ClientException(_('No such confirmation code.'), 403); // TRANS: Client exception thrown when given confirmation code was not issued.
throw new ClientException(_m('No such confirmation code.'), 403);
} }
} }
$this->password1 = $this->trimmed('password1'); $this->password1 = $this->trimmed('password1');
$this->password2 = $this->trimmed('password2'); $this->password2 = $this->trimmed('password2');
$this->tos = $this->boolean('tos'); $this->tos = $this->boolean('tos');
} }
} else { // GET } else { // GET
@ -128,7 +129,8 @@ class EmailregisterAction extends Action
$this->confirmation = Confirm_address::staticGet('code', $this->code); $this->confirmation = Confirm_address::staticGet('code', $this->code);
if (empty($this->confirmation)) { if (empty($this->confirmation)) {
throw new ClientException(_('No such confirmation code.'), 405); // TRANS: Client exception thrown when given confirmation code was not issued.
throw new ClientException(_m('No such confirmation code.'), 405);
} }
} }
} }
@ -148,7 +150,7 @@ class EmailregisterAction extends Action
case self::SETPASSWORD: case self::SETPASSWORD:
case self::CONFIRMINVITE: case self::CONFIRMINVITE:
case self::CONFIRMREGISTER: case self::CONFIRMREGISTER:
// TRANS: Title for page where to change password. // TRANS: Title for page where to register with a confirmation code.
return _m('TITLE','Complete registration'); return _m('TITLE','Complete registration');
break; break;
} }
@ -202,7 +204,8 @@ class EmailregisterAction extends Action
$old = User::staticGet('email', $this->email); $old = User::staticGet('email', $this->email);
if (!empty($old)) { if (!empty($old)) {
$this->error = sprintf(_('A user with that email address already exists. You can use the '. // TRANS: Error text when trying to register with an already registered e-mail address.
$this->error = sprintf(_m('A user with that email address already exists. You can use the '.
'<a href="%s">password recovery</a> tool to recover a missing password.'), '<a href="%s">password recovery</a> tool to recover a missing password.'),
common_local_url('recoverpassword')); common_local_url('recoverpassword'));
$this->showRegistrationForm(); $this->showRegistrationForm();
@ -217,7 +220,8 @@ class EmailregisterAction extends Action
Event::handle('EndValidateUserEmail', array(null, $this->email, &$valid)); Event::handle('EndValidateUserEmail', array(null, $this->email, &$valid));
} }
if (!$valid) { if (!$valid) {
$this->error = _('Not a valid email address.'); // TRANS: Error text when trying to register with an invalid e-mail address.
$this->error = _m('Not a valid email address.');
$this->showRegistrationForm(); $this->showRegistrationForm();
return; return;
} }
@ -231,17 +235,19 @@ class EmailregisterAction extends Action
if (empty($confirm)) { if (empty($confirm)) {
$confirm = Confirm_address::saveNew(null, $this->email, 'register'); $confirm = Confirm_address::saveNew(null, $this->email, 'register');
$prompt = sprintf(_('An email was sent to %s to confirm that address. Check your email inbox for instructions.'), // TRANS: Confirmation text after initial registration.
$prompt = sprintf(_m('An email was sent to %s to confirm that address. Check your email inbox for instructions.'),
$this->email); $this->email);
} else { } else {
$prompt = sprintf(_('The address %s was already registered but not confirmed. The confirmation code was resent.'), // TRANS: Confirmation text after re-requesting an e-mail confirmation code.
$prompt = sprintf(_m('The address %s was already registered but not confirmed. The confirmation code was resent.'),
$this->email); $this->email);
} }
$this->sendConfirmEmail($confirm); $this->sendConfirmEmail($confirm);
$this->complete = $prompt; $this->complete = $prompt;
$this->showPage(); $this->showPage();
} }
@ -252,7 +258,7 @@ class EmailregisterAction extends Action
} else if (!empty($this->confirmation)) { } else if (!empty($this->confirmation)) {
$email = $this->confirmation->address; $email = $this->confirmation->address;
} }
$nickname = $this->nicknameFromEmail($email); $nickname = $this->nicknameFromEmail($email);
$this->form = new ConfirmRegistrationForm($this, $this->form = new ConfirmRegistrationForm($this,
@ -342,11 +348,15 @@ class EmailregisterAction extends Action
$headers['From'] = mail_notify_from(); $headers['From'] = mail_notify_from();
$headers['To'] = trim($confirm->address); $headers['To'] = trim($confirm->address);
$headers['Subject'] = sprintf(_('Confirm your registration on %1$s'), $sitename); // TRANS: Subject for confirmation e-mail.
// TRANS: %s is the StatusNet sitename.
$headers['Subject'] = sprintf(_m('Confirm your registration on %s'), $sitename);
$confirmUrl = common_local_url('register', array('code' => $confirm->code)); $confirmUrl = common_local_url('register', array('code' => $confirm->code));
$body = sprintf(_('Someone (probably you) has requested an account on %1$s using this email address.'. // TRANS: Body for confirmation e-mail.
// TRANS: %1$s is the StatusNet sitename, %2$s is the confirmation URL.
$body = sprintf(_m('Someone (probably you) has requested an account on %1$s using this email address.'.
"\n". "\n".
'To confirm the address, click the following URL or copy it into the address bar of your browser.'. 'To confirm the address, click the following URL or copy it into the address bar of your browser.'.
"\n". "\n".
@ -387,7 +397,6 @@ class EmailregisterAction extends Action
* *
* @return boolean is read only action? * @return boolean is read only action?
*/ */
function isReadOnly($args) function isReadOnly($args)
{ {
return false; return false;
@ -396,9 +405,9 @@ class EmailregisterAction extends Action
function nicknameFromEmail($email) function nicknameFromEmail($email)
{ {
$parts = explode('@', $email); $parts = explode('@', $email);
$nickname = $parts[0]; $nickname = $parts[0];
$nickname = preg_replace('/[^A-Za-z0-9]/', '', $nickname); $nickname = preg_replace('/[^A-Za-z0-9]/', '', $nickname);
$nickname = Nickname::normalize($nickname); $nickname = Nickname::normalize($nickname);
@ -422,7 +431,6 @@ class EmailregisterAction extends Action
* *
* @return void * @return void
*/ */
function showLocalNav() function showLocalNav()
{ {
$nav = new LoginGroupNav($this); $nav = new LoginGroupNav($this);

View File

@ -4,7 +4,7 @@
* Copyright (C) 2011, StatusNet, Inc. * Copyright (C) 2011, StatusNet, Inc.
* *
* Email registration form * Email registration form
* *
* PHP version 5 * PHP version 5
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -44,7 +44,6 @@ if (!defined('STATUSNET')) {
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
class EmailRegistrationForm extends Form class EmailRegistrationForm extends Form
{ {
protected $email; protected $email;
@ -58,14 +57,15 @@ class EmailRegistrationForm extends Form
function formData() function formData()
{ {
$this->out->element('p', 'instructions', $this->out->element('p', 'instructions',
_('Enter your email address to register for an account.')); // TRANS: Form instructions.
_m('Enter your email address to register for an account.'));
$this->out->elementStart('fieldset', array('id' => 'new_bookmark_data')); $this->out->elementStart('fieldset', array('id' => 'new_bookmark_data'));
$this->out->elementStart('ul', 'form_data'); $this->out->elementStart('ul', 'form_data');
$this->li(); $this->li();
$this->out->input('email', $this->out->input('email',
// TRANS: Field label on form for adding a new bookmark. // TRANS: Field label on form for registering an account.
_m('LABEL','E-mail address'), _m('LABEL','E-mail address'),
$this->email); $this->email);
$this->unli(); $this->unli();
@ -87,10 +87,9 @@ class EmailRegistrationForm extends Form
* *
* @return void * @return void
*/ */
function formActions() function formActions()
{ {
// TRANS: Button text for action to save a new bookmark. // TRANS: Button text for registering an account.
$this->out->submit('submit', _m('BUTTON', 'Register')); $this->out->submit('submit', _m('BUTTON', 'Register'));
} }
@ -102,7 +101,6 @@ class EmailRegistrationForm extends Form
* *
* @return int ID of the form * @return int ID of the form
*/ */
function id() function id()
{ {
return 'form_email_registration'; return 'form_email_registration';
@ -116,7 +114,6 @@ class EmailRegistrationForm extends Form
* *
* @return string URL to post to * @return string URL to post to
*/ */
function action() function action()
{ {
return common_local_url('register'); return common_local_url('register');
@ -127,4 +124,3 @@ class EmailRegistrationForm extends Form
return 'form_email_registration form_settings'; return 'form_email_registration form_settings';
} }
} }

View File

@ -299,6 +299,13 @@ class QnAPlugin extends MicroAppPlugin
if ($nli->notice->scope != 0 && $nli->notice->scope != 1) { if ($nli->notice->scope != 0 && $nli->notice->scope != 1) {
$class .= ' limited-scope'; $class .= ' limited-scope';
} }
$question = QnA_Question::staticGet('uri', $nli->notice->uri);
if (!empty($question->closed)) {
$class .= ' closed';
}
$nli->out->elementStart( $nli->out->elementStart(
'li', array( 'li', array(
'class' => $class, 'class' => $class,
@ -375,20 +382,10 @@ class QnAPlugin extends MicroAppPlugin
$question = QnA_Question::getByNotice($notice); $question = QnA_Question::getByNotice($notice);
if (!empty($question)) { if (!empty($question)) {
if (empty($user)) {
$form = new QnashowquestionForm($out, $question); $form = new QnashowquestionForm($out, $question);
$form->show(); $form->show();
} else {
$profile = $user->getProfile();
$answer = $question->getAnswer($profile);
if (empty($answer)) {
$form = new QnanewanswerForm($out, $question);
$form->show();
} else {
$form = new QnashowquestionForm($out, $question);
$form->show();
}
}
} else { } else {
$out->text(_m('Question data is missing.')); $out->text(_m('Question data is missing.'));
} }
@ -398,6 +395,71 @@ class QnAPlugin extends MicroAppPlugin
$out->elementStart('div', array('class' => 'entry-content')); $out->elementStart('div', array('class' => 'entry-content'));
} }
/**
* Output the HTML for this kind of object in a list
*
* @param NoticeListItem $nli The list item being shown.
*
* @return boolean hook value
*
* @fixme WARNING WARNING WARNING this closes a 'div' that is implicitly opened in BookmarkPlugin's showNotice implementation
*/
function onStartShowNoticeItem($nli)
{
if (!$this->isMyNotice($nli->notice)) {
return true;
}
$out = $nli->out;
$notice = $nli->notice;
$this->showNotice($notice, $out);
$nli->showNoticeLink();
$nli->showNoticeSource();
$nli->showNoticeLocation();
$nli->showContext();
$nli->showRepeat();
$out->elementEnd('div');
$nli->showNoticeOptions();
if ($notice->object_type == QnA_Question::OBJECT_TYPE) {
$user = common_current_user();
$question = QnA_Question::getByNotice($notice);
if (!empty($user)) {
$profile = $user->getProfile();
$answer = $question->getAnswer($profile);
// Output a placeholder input -- clicking on it will
// bring up a real answer form
// NOTE: this whole ul is just a placeholder
if (empty($question->closed) && empty($answer)) {
$out->elementStart('ul', 'notices qna-dummy');
$out->elementStart('li', 'qna-dummy-placeholder');
$out->element(
'input',
array(
'class' => 'placeholder',
'value' => _m('Your answer...')
)
);
$out->elementEnd('li');
$out->elementEnd('ul');
}
}
}
return false;
}
function showNoticeAnswer($notice, $out) function showNoticeAnswer($notice, $out)
{ {
$user = common_current_user(); $user = common_current_user();

View File

@ -49,7 +49,7 @@ class QnanewanswerAction extends Action
protected $error = null; protected $error = null;
protected $complete = null; protected $complete = null;
protected $question = null; public $question = null;
protected $content = null; protected $content = null;
/** /**
@ -76,7 +76,7 @@ class QnanewanswerAction extends Action
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
StatusNet::setApi(true); StatusNet::setApi(true);
} }
common_debug("in qnanewanswer");
$this->user = common_current_user(); $this->user = common_current_user();
if (empty($this->user)) { if (empty($this->user)) {
@ -93,8 +93,6 @@ class QnanewanswerAction extends Action
$id = substr($this->trimmed('id'), 9); $id = substr($this->trimmed('id'), 9);
common_debug("XXXXXXXXXXXXXXXXXX id = " . $id);
$this->question = QnA_Question::staticGet('id', $id); $this->question = QnA_Question::staticGet('id', $id);
if (empty($this->question)) { if (empty($this->question)) {
@ -124,7 +122,7 @@ class QnanewanswerAction extends Action
if ($this->isPost()) { if ($this->isPost()) {
$this->newAnswer(); $this->newAnswer();
} else { } else {
$this->showPage(); $this->showForm();
} }
return; return;
@ -147,7 +145,7 @@ class QnanewanswerAction extends Action
); );
} catch (ClientException $ce) { } catch (ClientException $ce) {
$this->error = $ce->getMessage(); $this->error = $ce->getMessage();
$this->showPage(); $this->showForm($this->error);
return; return;
} }
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
@ -164,33 +162,14 @@ class QnanewanswerAction extends Action
$this->elementStart('body'); $this->elementStart('body');
$nli = new NoticeListItem($notice, $this);
$nli = new NoticeAnswerListItem($notice, $this, $this->question, $answer);
$nli->show(); $nli->show();
//$this->raw($answer->asHTML());
/*
$question = $this->question;
$nli = new NoticeListItem($notice, $this);
$nli->showNotice();
$this->elementStart('div', array('class' => 'entry-content answer-content'));
if (!empty($answer)) {
$form = new QnashowanswerForm($this, $answer);
$form->show();
} else {
$this->text(_m('Answer data is missing.'));
}
$this->elementEnd('div');
// @fixme
//$this->elementStart('div', array('class' => 'entry-content'));
*/
$this->elementEnd('body'); $this->elementEnd('body');
$this->elementEnd('html'); $this->elementEnd('html');
} else { } else {
common_debug("not ajax");
common_redirect($this->question->bestUrl(), 303); common_redirect($this->question->bestUrl(), 303);
} }
} }
@ -230,4 +209,145 @@ class QnanewanswerAction extends Action
return false; return false;
} }
} }
/**
* Show an Ajax-y error message
*
* Goes back to the browser, where it's shown in a popup.
*
* @param string $msg Message to show
*
* @return void
*/
function ajaxErrorMsg($msg)
{
$this->startHTML('text/xml;charset=utf-8', true);
$this->elementStart('head');
// TRANS: Page title after an AJAX error occurs on the post answer page.
$this->element('title', null, _('Ajax Error'));
$this->elementEnd('head');
$this->elementStart('body');
$this->element('p', array('id' => 'error'), $msg);
$this->elementEnd('body');
$this->elementEnd('html');
}
/**
* Show an Ajax-y answer form
*
* Goes back to the browser, where it's shown in a popup.
*
* @param string $msg Message to show
*
* @return void
*/
function ajaxShowForm()
{
common_debug('ajaxShowForm()');
$this->startHTML('text/xml;charset=utf-8', true);
$this->elementStart('head');
// TRANS: Title for form to send answer to a question.
$this->element('title', null, _m('TITLE','Your answer'));
$this->elementEnd('head');
$this->elementStart('body');
$form = new QnanewanswerForm($this, $this->question);
$form->show();
$this->elementEnd('body');
$this->elementEnd('html');
}
/**
* @param string $msg An error message, if any
*
* @return void
*/
function showForm($msg = null)
{
common_debug("show form - msg = $msg");
if ($this->boolean('ajax')) {
if ($msg) {
$this->ajaxErrorMsg($msg);
} else {
$this->ajaxShowForm();
}
return;
}
$this->msg = $msg;
$this->showPage();
}
} }
class NoticeAnswerListItem extends NoticeListItem
{
protected $question;
protected $answer;
/**
* constructor
*
* Also initializes the profile attribute.
*
* @param Notice $notice The notice we'll display
*/
function __construct($notice, $out=null, $question, $answer)
{
parent::__construct($notice, $out);
$this->question = $question;
$this->answer = $answer;
}
function show()
{
if (empty($this->notice)) {
common_log(LOG_WARNING, "Trying to show missing notice; skipping.");
return;
} else if (empty($this->profile)) {
common_log(LOG_WARNING, "Trying to show missing profile (" . $this->notice->profile_id . "); skipping.");
return;
}
$this->showStart();
$this->showNotice();
$this->showNoticeInfo();
$notice = $this->question->getNotice();
$this->out->hidden('inreplyto', $notice->id);
$this->showEnd();
}
/**
* show the content of the notice
*
* Shows the content of the notice. This is pre-rendered for efficiency
* at save time. Some very old notices might not be pre-rendered, so
* they're rendered on the spot.
*
* @return void
*/
function showContent()
{
$this->out->elementStart('p', array('class' => 'entry-content answer-content'));
if ($this->notice->rendered) {
$this->out->raw($this->notice->rendered);
} else {
// XXX: may be some uncooked notices in the DB,
// we cook them right now. This should probably disappear in future
// versions (>> 0.4.x)
$this->out->raw(common_render_content($this->notice->content, $this->notice));
}
if (!empty($this->answer)) {
$form = new QnashowanswerForm($this->out, $this->answer);
$form->show();
} else {
$out->text(_m('Answer data is missing.'));
}
$this->out->elementEnd('p');
}
}

View File

@ -175,8 +175,7 @@ class QnanewquestionAction extends Action
*/ */
function showNotice($notice) function showNotice($notice)
{ {
class_exists('NoticeList'); // @fixme hack for autoloader $nli = new NoticeQuestionListItem($notice, $this);
$nli = new NoticeListItem($notice, $this);
$nli->show(); $nli->show();
} }
@ -221,3 +220,25 @@ class QnanewquestionAction extends Action
} }
} }
} }
class NoticeQuestionListItem extends NoticeListItem
{
/**
* constructor
*
* Also initializes the profile attribute.
*
* @param Notice $notice The notice we'll display
*/
function __construct($notice, $out=null)
{
parent::__construct($notice, $out);
}
function showEnd()
{
$this->out->element('ul', 'notices threaded-replies xoxo');
parent::showEnd();
}
}

View File

@ -217,6 +217,14 @@ class QnA_Question extends Managed_DataObject
$out = new XMLStringer(); $out = new XMLStringer();
$cls = array('qna_question');
if (!empty($question->closed)) {
$cls[] = 'closed';
}
$out->elementStart('p', array('class' => implode(' ', $cls)));
if (!empty($question->description)) { if (!empty($question->description)) {
$out->elementStart('span', 'question-description'); $out->elementStart('span', 'question-description');
$out->raw(QnAPlugin::shorten($question->description, $notice)); $out->raw(QnAPlugin::shorten($question->description, $notice));
@ -237,6 +245,8 @@ class QnA_Question extends Managed_DataObject
$out->elementEnd('span'); $out->elementEnd('span');
} }
$out->elementEnd('p');
return $out->getString(); return $out->getString();
} }

View File

@ -1 +1,5 @@
/* stubb for q&a css */ /* Why doesn't this work? */
input.answer-placeholder {
margin-left: 0;
width: 95%;
}

View File

@ -1,23 +1,355 @@
var QnA = { var QnA = {
// hide all the 'close' and 'best' buttons for this question
// @fixme: Should use ID // @fixme: Should use ID
close: function(closeButt) { close: function(form, best) {
$(closeButt) var notice = $(form).closest('li.hentry.notice.question');
.closest('li.hentry.notice.question')
.find('input[name=best],[name=close]') notice.find('input#qna-best-answer,#qna-question-close').hide();
.hide(); notice.find('textarea').hide();
var list = notice.find('ul');
notice.find('ul > li.notice-answer-placeholder').remove();
notice.find('ul > li.notice-answer').remove();
if (best) {
var p = notice.parent().find('div.question-description > form > fieldset > p');
if (p.length != 0) {
p.append($('<span class="question-closed">This question is closed.</span>'));
}
}
}, },
init: function() { init: function() {
var that = this; QnA.NoticeInlineAnswerSetup();
$('input[name=close]').live('click', function() {
that.close(this); $('form.form_question_show').live('submit', function() {
QnA.close(this);
}); });
$('input[name=best]').live('click', function() { $('form.form_answer_show').live('submit', function() {
that.close(this); QnA.close(this, true);
});
},
/**
* Open up a question's inline answer box.
*
* @param {jQuery} notice: jQuery object containing one notice
*/
NoticeInlineAnswerTrigger: function(notice) {
// Find the notice we're replying to...
var id = $($('.notice_id', notice)[0]).text();
var parentNotice = notice;
// Find the threaded replies view we'll be adding to...
var list = notice.closest('.notices');
if (list.hasClass('threaded-replies')) {
// We're replying to a reply; use reply form on the end of this list.
// We'll add our form at the end of this; grab the root notice.
parentNotice = list.closest('.notice');
} else {
// We're replying to a parent notice; pull its threaded list
// and we'll add on the end of it. Will add if needed.
list = $('ul.threaded-replies', notice);
}
// See if the form's already open...
var answerForm = $('.qna_answer_form', list);
var hideReplyPlaceholders = function(notice) {
// Do we still have a dummy answer placeholder? If so get rid of
// reply place holders for this question. If the current user hasn't
// answered the question we want to direct her to providing an
// answer. She can still reply by hitting the reply button if she
// really wants to.
var dummyAnswer = $('ul.qna-dummy', notice);
if (dummyAnswer.length > 0) {
notice.find('li.notice-reply-placeholder').hide();
}
}
var nextStep = function() {
var dummyAnswer = $('ul.qna-dummy', notice);
dummyAnswer.hide();
// Set focus...
var text = answerForm.find('textarea');
if (text.length == 0) {
throw "No textarea";
}
text.focus();
$('body').click(function(e) {
var dummyAnswer = $('ul.qna-dummy', notice);
var style = dummyAnswer.attr('style');
var ans = $(notice).find('li.hentry.notice.anwer', notice)
if (ans > 0) {
hideReplyPlaceholders(notice);
}
var openAnswers = $('li.notice-answer');
if (openAnswers.length > 0) {
var target = $(e.target);
openAnswers.each(function() {
// Did we click outside this one?
var answerItem = $(this);
var parentNotice = answerItem.closest('li.notice');
if (answerItem.has(e.target).length == 0) {
var textarea = answerItem.find('.notice_data-text:first');
var cur = $.trim(textarea.val());
// Only close if there's been no edit.
if (cur == '' || cur == textarea.data('initialText')) {
answerItem.remove();
dummyAnswer.show();
}
}
});
}
});
};
// See if the form's already open...
if (answerForm.length > 0 ) {
nextStep();
} else {
var placeholder = list.find('li.qna-dummy-placeholder').hide();
// Create the answer form entry at the end
var answerItem = $('li.notice-answer', list);
if (answerItem.length == 0) {
answerItem = $('<li class="notice-answer"></li>');
var intermediateStep = function(formMaster) {
// @todo cache the form if we can (worth it?)
var formEl = document._importNode(formMaster, true);
$(formEl).data('NoticeFormSetup', true);
answerItem.append(formEl);
list.prepend(answerItem); // *before* the placeholder
var form = answerForm = $(formEl);
QnA.AnswerFormSetup(form);
nextStep();
};
if (QnA.AnswerFormMaster) {
// @todo if we had a cached for here's where we'd use it'
intermediateStep(QnA.AnswerFormMaster);
} else {
// Fetch a fresh copy of the answer form over AJAX.
// Warning: this can have a delay, which looks bad.
// @fixme this fallback may or may not work
var url = $('#answer-action').attr('value');
$.get(url, {ajax: 1}, function(data, textStatus, xhr) {
intermediateStep($('form', data)[0]);
});
}
}
}
},
/**
* Setup function -- DOES NOT apply immediately.
*
* Sets up event handlers for inline reply mini-form placeholders.
* Uses 'live' rather than 'bind', so applies to future as well as present items.
*/
NoticeInlineAnswerSetup: function() {
$('li.qna-dummy-placeholder input.placeholder')
.live('focus', function() {
var notice = $(this).closest('li.notice');
QnA.NoticeInlineAnswerTrigger(notice);
return false;
});
},
AnswerFormSetup: function(form) {
form.find('textarea').focus();
if (!form.data('NoticeFormSetup')) {
alert('gargargar');
}
if (!form.data('AnswerFormSetup')) {
//SN.U.NoticeLocationAttach(form);
QnA.FormAnswerXHR(form);
//SN.U.FormNoticeEnhancements(form);
//SN.U.NoticeDataAttach(form);
form.data('NoticeFormSetup', true);
}
},
/**
* Setup function -- DOES NOT trigger actions immediately.
*
* Sets up event handlers for special-cased async submission of the
* answer-posting form, including some pre-post validation.
*
* @fixme geodata
* @fixme refactor and unify with FormNoticeXHR in util.js
*
* @param {jQuery} form: jQuery object whose first element is a form
*
* @access public
*/
FormAnswerXHR: function(form) {
//SN.C.I.NoticeDataGeo = {};
form.append('<input type="hidden" name="ajax" value="1"/>');
// Make sure we don't have a mixed HTTP/HTTPS submission...
form.attr('action', SN.U.RewriteAjaxAction(form.attr('action')));
/**
* Show a response feedback bit under the new-notice dialog.
*
* @param {String} cls: CSS class name to use ('error' or 'success')
* @param {String} text
* @access private
*/
var showFeedback = function(cls, text) {
form.append(
$('<p class="form_response"></p>')
.addClass(cls)
.text(text)
);
};
/**
* Hide the previous response feedback, if any.
*/
var removeFeedback = function() {
form.find('.form_response').remove();
};
form.ajaxForm({
dataType: 'xml',
timeout: '60000',
beforeSend: function(formData) {
if (form.find('.notice_data-text:first').val() == '') {
form.addClass(SN.C.S.Warning);
return false;
}
form
.addClass(SN.C.S.Processing)
.find('.submit')
.addClass(SN.C.S.Disabled)
.attr(SN.C.S.Disabled, SN.C.S.Disabled);
return true;
},
error: function (xhr, textStatus, errorThrown) {
form
.removeClass(SN.C.S.Processing)
.find('.submit')
.removeClass(SN.C.S.Disabled)
.removeAttr(SN.C.S.Disabled, SN.C.S.Disabled);
removeFeedback();
if (textStatus == 'timeout') {
// @fixme i18n
showFeedback('error', 'Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.');
}
else {
var response = SN.U.GetResponseXML(xhr);
if ($('.'+SN.C.S.Error, response).length > 0) {
form.append(document._importNode($('.'+SN.C.S.Error, response)[0], true));
}
else {
if (parseInt(xhr.status) === 0 || jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) {
form
.resetForm()
.find('.attach-status').remove();
SN.U.FormNoticeEnhancements(form);
}
else {
// @fixme i18n
showFeedback('error', '(Sorry! We had trouble sending your notice ('+xhr.status+' '+xhr.statusText+'). Please report the problem to the site administrator if this happens again.');
}
}
}
},
success: function(data, textStatus) {
removeFeedback();
var errorResult = $('#'+SN.C.S.Error, data);
if (errorResult.length > 0) {
showFeedback('error', errorResult.text());
}
else {
// New notice post was successful. If on our timeline, show it!
var notice = document._importNode($('li', data)[0], true);
var notices = $('#notices_primary .notices:first');
var answerItem = form.closest('li.notice-answer');
var questionItem = form.closest('li.question');
var dummyAnswer = form.find('ul.qna-dummy', questionItem).remove();
if (answerItem.length > 0) {
// If this is an inline answer, remove the form...
var list = form.closest('.threaded-replies');
// if the inserted notice's parent question needs it give it a placeholder
var ans = questionItem.find('ul > li.hentry.notice.answer');
if (ans.length == 0) {
SN.U.NoticeInlineReplyPlaceholder(questionItem);
}
var id = $(notice).attr('id');
if ($("#"+id).length == 0) {
$(notice).insertBefore(answerItem);
answerItem.remove();
} else {
// NOP
// Realtime came through before us...
}
} else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) {
// Not a reply. If on our timeline, show it at the
if ($('#'+notice.id).length === 0) {
var notice_irt_value = form.find('#inreplyto').val();
var notice_irt = '#notices_primary #notice-'+notice_irt_value;
if($('body')[0].id == 'conversation') {
if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) {
$(notice_irt).append('<ul class="notices"></ul>');
}
$($(notice_irt+' .notices')[0]).append(notice);
}
else {
notices.prepend(notice);
}
$('#'+notice.id)
.css({display:'none'})
.fadeIn(2500);
}
// realtime injected the notice first
} else {
// Not on a timeline that this belongs on?
// Just show a success message.
// @fixme inline
showFeedback('success', $('title', data).text());
}
}
},
complete: function(xhr, textStatus) {
form
.removeClass(SN.C.S.Processing)
.find('.submit')
.removeAttr(SN.C.S.Disabled)
.removeClass(SN.C.S.Disabled);
}
}); });
} }
}; };

View File

@ -57,7 +57,7 @@ class QnanewanswerForm extends Form
* *
* @return void * @return void
*/ */
function __construct(HTMLOutputter $out, QnA_Question $question, $showQuestion = true) function __construct(HTMLOutputter $out, QnA_Question $question, $showQuestion = false)
{ {
parent::__construct($out); parent::__construct($out);
$this->question = $question; $this->question = $question;
@ -81,7 +81,7 @@ class QnanewanswerForm extends Form
*/ */
function formClass() function formClass()
{ {
return 'form_settings ajax'; return 'form_settings qna_answer_form ajax-notice';
} }
/** /**
@ -110,7 +110,7 @@ class QnanewanswerForm extends Form
} }
$out->hidden('qna-question-id', $id, 'id'); $out->hidden('qna-question-id', $id, 'id');
$out->textarea('qna-answer', 'answer', null, null, 'answer'); $out->textarea('qna-answer', _m('Enter your answer'), null, null, 'answer');
} }
/** /**

View File

@ -149,7 +149,7 @@ class QnashowanswerForm extends Form
// TRANS: Button text for marking an answer as "best" // TRANS: Button text for marking an answer as "best"
_m('BUTTON', 'Best'), _m('BUTTON', 'Best'),
'submit', 'submit',
'submit', 'best',
// TRANS: Title for button text marking an answer as "best" // TRANS: Title for button text marking an answer as "best"
_('Mark as best answer') _('Mark as best answer')
); );
@ -185,6 +185,6 @@ class QnashowanswerForm extends Form
*/ */
function formClass() function formClass()
{ {
return 'form_show ajax'; return 'form_answer_show ajax';
} }
} }

View File

@ -119,6 +119,15 @@ class QnashowquestionForm extends Form
'id' 'id'
); );
$this->out->hidden(
'answer-action',
common_local_url(
'qnanewanswer',
null,
array('id' => 'question-' . $this->question->id)
)
);
$this->out->raw($this->question->asHTML()); $this->out->raw($this->question->asHTML());
} }
@ -156,6 +165,6 @@ class QnashowquestionForm extends Form
*/ */
function formClass() function formClass()
{ {
return 'form_close ajax'; return 'form_question_show ajax';
} }
} }