diff --git a/actions/addpeopletag.php b/actions/addpeopletag.php new file mode 100644 index 0000000000..3176a116c9 --- /dev/null +++ b/actions/addpeopletag.php @@ -0,0 +1,172 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/togglepeopletag.php'; + +/** + * + * Action to tag a profile with a single tag. + * + * Takes parameters: + * + * - tagged: the ID of the profile being tagged + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * - peopletag_id: the ID of the tag being used + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class AddpeopletagAction extends Action +{ + var $user; + var $tagged; + var $peopletag; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + + function prepare($args) + { + parent::prepare($args); + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_('Not logged in.')); + return false; + } + + // Profile to subscribe to + + $tagged_id = $this->arg('tagged'); + + $this->tagged = Profile::staticGet('id', $tagged_id); + + if (empty($this->tagged)) { + $this->clientError(_('No such profile.')); + return false; + } + + $id = $this->arg('peopletag_id'); + $this->peopletag = Profile_list::staticGet('id', $id); + + if (empty($this->peopletag)) { + $this->clientError(_('No such peopletag.')); + return false; + } + + // OMB 0.1 doesn't have a mechanism for local-server- + // originated tag. + + $omb01 = Remote_profile::staticGet('id', $tagged_id); + + if (!empty($omb01)) { + $this->clientError(_('You cannot tag an OMB 0.1'. + ' remote profile with this action.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the tagging and returns results. + * + * @param Array $args unused. + * + * @return void + */ + + function handle($args) + { + + // Throws exception on error + $ptag = Profile_tag::setTag($this->user->id, $this->tagged->id, + $this->peopletag->tag); + + if (!$ptag) { + $user = User::staticGet('id', $id); + if ($user) { + $this->clientError( + sprintf(_('There was an unexpected error while tagging %s'), + $user->nickname)); + } else { + $this->clientError(sprintf(_('There was a problem tagging %s.' . + 'The remote server is probably not responding correctly, ' . + 'please try retrying later.'), $this->profile->profileurl)); + } + return false; + } + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _('Subscribed')); + $this->elementEnd('head'); + $this->elementStart('body'); + $unsubscribe = new UntagButton($this, $this->tagged, $this->peopletag); + $unsubscribe->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('subscriptions', + array('nickname' => $this->user->nickname)); + common_redirect($url, 303); + } + } +} diff --git a/actions/editpeopletag.php b/actions/editpeopletag.php new file mode 100644 index 0000000000..b00ccc8abb --- /dev/null +++ b/actions/editpeopletag.php @@ -0,0 +1,311 @@ +. + * + * @category Group + * @package StatusNet + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Add a new group + * + * This is the form for adding a new group + * + * @category Group + * @package StatusNet + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class EditpeopletagAction extends OwnerDesignAction +{ + + var $msg, $confirm, $confirm_args=array(); + + function title() + { + if ($_SERVER['REQUEST_METHOD'] == 'POST' && $this->boolean('delete')) { + return sprintf(_('Delete %s people tag'), $this->peopletag->tag); + } + return sprintf(_('Edit people tag %s'), $this->peopletag->tag); + } + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('Not logged in.')); + return false; + } + + $id = $this->arg('id'); + $tagger_arg = $this->arg('tagger'); + $tag_arg = $this->arg('tag'); + + $tagger = common_canonical_nickname($tagger_arg); + $tag = common_canonical_tag($tag_arg); + + $current = common_current_user(); + + // Permanent redirect on non-canonical tag + + if ($tagger_arg != $tagger || $tag_arg != $tag) { + $args = array('tagger' => $tagger, 'tag' => $tag); + common_redirect(common_local_url('editpeopletag', $args), 301); + return false; + } + + $user = null; + if ($id) { + $this->peopletag = Profile_list::staticGet('id', $id); + if (!empty($this->peopletag)) { + $user = User::staticGet('id', $this->peopletag->tagger); + } + } else { + if (!$tagger) { + $this->clientError(_('No tagger or ID.'), 404); + return false; + } + + $user = User::staticGet('nickname', $tagger); + $this->peopletag = Profile_list::pkeyGet(array('tagger' => $user->id, 'tag' => $tag)); + } + + if (!$this->peopletag) { + $this->clientError(_('No such peopletag.'), 404); + return false; + } + + if (!$user) { + // This should not be happening + $this->clientError(_('Not a local user.'), 404); + return false; + } + + if ($current->id != $user->id) { + $this->clientError(_('You must be the creator of the tag to edit it.'), 404); + return false; + } + + $this->tagger = $user->getProfile(); + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the group. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->trySave(); + } else { + $this->showForm(); + } + } + + function showConfirm($msg=null, $fwd=null) + { + $this->confirm = $msg; + $this->confirm_args = $fwd; + $this->showPage(); + } + + function showConfirmForm() + { + $this->elementStart('form', array('id' => 'form_peopletag_edit_confirm', + 'class' => 'form_settings', + 'method' => 'post', + 'action' => common_local_url('editpeopletag', + array('tagger' => $this->tagger->nickname, + 'tag' => $this->peopletag->tag)))); + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->hidden('id', $this->arg('id')); + + foreach ($this->confirm_args as $key => $val) { + $this->hidden($key, $val); + } + + $this->submit('form_action-no', + _m('BUTTON','No'), + 'submit form_action-primary', + 'cancel'); + $this->submit('form_action-yes', + _m('BUTTON','Yes'), + 'submit form_action-secondary', + 'confirm'); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function showForm($msg=null) + { + $this->msg = $msg; + $this->showPage(); + } + + function showLocalNav() + { + $nav = new PeopletagGroupNav($this, $this->peopletag); + $nav->show(); + } + + function showContent() + { + if ($this->confirm) { + $this->showConfirmForm(); + return; + } + + $form = new PeopletagEditForm($this, $this->peopletag); + $form->show(); + + $form->showProfileList(); + } + + function showPageNotice() + { + if ($this->msg) { + $this->element('p', 'error', $this->msg); + } else if ($this->confirm) { + $this->element('p', 'instructions', $this->confirm); + } else { + $this->element('p', 'instructions', + _('Use this form to edit the people tag.')); + } + } + + function showScripts() + { + parent::showScripts(); + $this->autofocus('tag'); + } + + function trySave() + { + $tag = common_canonical_tag($this->trimmed('tag')); + $description = $this->trimmed('description'); + $private = $this->boolean('private'); + $delete = $this->arg('delete'); + $confirm = $this->arg('confirm'); + $cancel = $this->arg('cancel'); + + if ($delete && $cancel) { + $this->showForm(_('Delete aborted.')); + return; + } + + $set_private = $private && $this->peopletag->private != $private; + + if ($delete && !$confirm) { + $this->showConfirm(_('Deleting this tag will permanantly remove ' . + 'all its subscription and membership records. ' . + 'Do you still want to continue?'), array('delete' => 1)); + return; + } else if (common_valid_tag($tag)) { + $this->showForm(_('Invalid tag.')); + return; + } else if ($tag != $this->peopletag->tag && $this->tagExists($tag)) { + $this->showForm(sprintf(_('You already have a tag named %s.'), $tag)); + return; + } else if (Profile_list::descriptionTooLong($description)) { + $this->showForm(sprintf(_('description is too long (max %d chars).'), Profile_list::maxDescription())); + return; + } else if ($set_private && !$confirm && !$cancel) { + $fwd = array('tag' => $tag, + 'description' => $description, + 'private' => (int) $private); + + $this->showConfirm(_('Setting a public tag as private will ' . + 'permanently remove all the existing ' . + 'subscriptions to it. Do you still want to continue?'), $fwd); + return; + } + + $this->peopletag->query('BEGIN'); + + $orig = clone($this->peopletag); + + $this->peopletag->tag = $tag; + $this->peopletag->description = $description; + if (!$set_private || $confirm) { + $this->peopletag->private = $private; + } + + $result = $this->peopletag->update($orig); + + if (!$result) { + common_log_db_error($this->group, 'UPDATE', __FILE__); + $this->serverError(_('Could not update peopletag.')); + } + + $this->peopletag->query('COMMIT'); + + if ($set_private && $confirm) { + Profile_tag_subscription::cleanup($this->peopletag); + } + + if ($delete) { + // This might take quite a bit of time. + $this->peopletag->delete(); + // send home. + common_redirect(common_local_url('all', + array('nickname' => $this->tagger->nickname)), + 303); + } + + if ($tag != $orig->tag) { + common_redirect(common_local_url('editpeopletag', + array('tagger' => $this->tagger->nickname, + 'tag' => $tag)), + 303); + } else { + $this->showForm(_('Options saved.')); + } + } + + function tagExists($tag) + { + $args = array('tagger' => $this->tagger->id, 'tag' => $tag); + $ptag = Profile_list::pkeyGet($args); + + return !empty($ptag); + } +} diff --git a/actions/peopletag.php b/actions/peopletag.php index 7287cfbf99..d549586f13 100644 --- a/actions/peopletag.php +++ b/actions/peopletag.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Action for showing profiles self-tagged with a given tag + * People tags by a user * * PHP version 5 * @@ -16,13 +16,16 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * + * PHP version 5 + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Action + * @category Personal * @package StatusNet * @author Evan Prodromou * @author Zach Copley + * @author Shashi Gowda * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -32,149 +35,125 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -/** - * This class outputs a paginated list of profiles self-tagged with a given tag - * - * @category Output - * @package StatusNet - * @author Evan Prodromou - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see Action - */ +require_once INSTALLDIR.'/lib/peopletaglist.php'; +// cache 3 pages +define('PEOPLETAG_CACHE_WINDOW', PEOPLETAGS_PER_PAGE*3 + 1); class PeopletagAction extends Action { - - var $tag = null; var $page = null; + var $tag = null; - /** - * For initializing members of the class. - * - * @param array $argarray misc. arguments - * - * @return boolean true - */ - function prepare($argarray) + function isReadOnly($args) { - parent::prepare($argarray); + return true; + } - $this->tag = $this->trimmed('tag'); - - if (!common_valid_profile_tag($this->tag)) { - $this->clientError(sprintf(_('Not a valid people tag: %s.'), - $this->tag)); - return; + function title() + { + if ($this->page == 1) { + return sprintf(_("Public people tag %s"), $this->tag); + } else { + return sprintf(_("Public people tag %s, page %d"), $this->tag, $this->page); } + } - $this->page = ($this->arg('page')) ? $this->arg('page') : 1; + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; - common_set_returnto($this->selfUrl()); + $tag_arg = $this->arg('tag'); + $tag = common_canonical_tag($tag_arg); + + // Permanent redirect on non-canonical nickname + + if ($tag_arg != $tag) { + $args = array('tag' => $nickname); + if ($this->page && $this->page != 1) { + $args['page'] = $this->page; + } + common_redirect(common_local_url('peopletag', $args), 301); + return false; + } + $this->tag = $tag; return true; } - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return boolean is read only action? - */ - function handle($argarray) + function handle($args) { - parent::handle($argarray); + parent::handle($args); $this->showPage(); } - /** - * Whips up a query to get a list of profiles based on the provided - * people tag and page, initalizes a ProfileList widget, and displays - * it to the user. - * - * @return nothing - */ + function showLocalNav() + { + $nav = new PublicGroupNav($this); + $nav->show(); + } + + function showAnonymousMessage() + { + $notice = + _('People tags are how you sort similar ' . + 'people on %%site.name%%, a [micro-blogging]' . + '(http://en.wikipedia.org/wiki/Micro-blogging) service ' . + 'based on the Free Software [StatusNet](http://status.net/) tool. ' . + 'You can then easily keep track of what they ' . + 'are doing by subscribing to the tag\'s timeline.' ); + $this->elementStart('div', array('id' => 'anon_notice')); + $this->raw(common_markup_to_html($notice)); + $this->elementEnd('div'); + } + function showContent() { + $offset = ($this->page-1) * PEOPLETAGS_PER_PAGE; + $limit = PEOPLETAGS_PER_PAGE + 1; - $profile = new Profile(); + $ptags = new Profile_list(); + $ptags->tag = $this->tag; - $offset = ($this->page - 1) * PROFILES_PER_PAGE; - $limit = PROFILES_PER_PAGE + 1; + $user = common_current_user(); - if (common_config('db', 'type') == 'pgsql') { - $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset; + if (empty($user)) { + $ckey = sprintf('profile_list:tag:%s', $this->tag); + $ptags->private = false; + $ptags->orderBy('profile_list.modified DESC'); + + $c = Cache::instance(); + if ($offset+$limit <= PEOPLETAG_CACHE_WINDOW && !empty($c)) { + $cached_ptags = Profile_list::getCached($ckey, $offset, $limit); + if ($cached_ptags === false) { + $ptags->limit(0, PEOPLETAG_CACHE_WINDOW); + $ptags->find(); + + Profile_list::setCache($ckey, $ptags, $offset, $limit); + } else { + $ptags = clone($cached_ptags); + } + } else { + $ptags->limit($offset, $limit); + $ptags->find(); + } } else { - $lim = ' LIMIT ' . $offset . ', ' . $limit; + $ptags->whereAdd('(profile_list.private = false OR (' . + ' profile_list.tagger =' . $user->id . + ' AND profile_list.private = true) )'); + + $ptags->orderBy('profile_list.modified DESC'); + $ptags->find(); } - // XXX: memcached this + $pl = new PeopletagList($ptags, $this); + $cnt = $pl->show(); - $qry = 'SELECT profile.* ' . - 'FROM profile JOIN profile_tag ' . - 'ON profile.id = profile_tag.tagger ' . - 'WHERE profile_tag.tagger = profile_tag.tagged ' . - "AND tag = '%s' " . - 'ORDER BY profile_tag.modified DESC%s'; - - $profile->query(sprintf($qry, $this->tag, $lim)); - - $ptl = new PeopleTagList($profile, $this); // pass the ammunition - $cnt = $ptl->show(); - - $this->pagination($this->page > 1, - $cnt > PROFILES_PER_PAGE, - $this->page, - 'peopletag', - array('tag' => $this->tag)); + $this->pagination($this->page > 1, $cnt > PEOPLETAGS_PER_PAGE, + $this->page, 'peopletag', array('tag' => $this->tag)); } - /** - * Returns the page title - * - * @return string page title - */ - function title() + function showSections() { - return sprintf(_('Users self-tagged with %1$s - page %2$d'), - $this->tag, $this->page); - } - -} - -class PeopleTagList extends ProfileList -{ - function newListItem($profile) - { - return new PeopleTagListItem($profile, $this->action); } } - -class PeopleTagListItem extends ProfileListItem -{ - function linkAttributes() - { - $aAttrs = parent::linkAttributes(); - - if (common_config('nofollow', 'peopletag')) { - $aAttrs['rel'] .= ' nofollow'; - } - - return $aAttrs; - } - - function homepageAttributes() - { - $aAttrs = parent::linkAttributes(); - - if (common_config('nofollow', 'peopletag')) { - $aAttrs['rel'] = 'nofollow'; - } - - return $aAttrs; - } -} - diff --git a/actions/peopletagautocomplete.php b/actions/peopletagautocomplete.php new file mode 100644 index 0000000000..db11a24667 --- /dev/null +++ b/actions/peopletagautocomplete.php @@ -0,0 +1,104 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class PeopletagautocompleteAction extends Action +{ + var $user; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + + function prepare($args) + { + parent::prepare($args); + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_('Not logged in.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + + function handle($args) + { + $profile = $this->user->getProfile(); + $tags = $profile->getOwnedTags(common_current_user()); + + $tags_array = array(); + while ($tags->fetch()) { + $arr = array(); + $arr['tag'] = $tags->tag; + $arr['mode'] = $tags->private ? 'private' : 'public'; + // $arr['url'] = $tags->homeUrl(); + $arr['freq'] = $tags->taggedCount(); + + $tags_array[] = $arr; + } + + $tags->free(); + + //common_log(LOG_DEBUG, 'Autocomplete data: ' . json_encode($tags_array)); + print(json_encode($tags_array)); + exit(0); + } +} diff --git a/actions/peopletagged.php b/actions/peopletagged.php new file mode 100644 index 0000000000..5038d1e213 --- /dev/null +++ b/actions/peopletagged.php @@ -0,0 +1,223 @@ +. + * + * @category Group + * @package StatusNet + * @author Shashi Gowda + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once(INSTALLDIR.'/lib/profilelist.php'); + +/** + * List of people tagged by the user with a tag + * + * @category Peopletag + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class PeopletaggedAction extends OwnerDesignAction +{ + var $page = null; + var $peopletag = null; + var $tagger = null; + + function isReadOnly($args) + { + return true; + } + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + $tagger_arg = $this->arg('tagger'); + $tag_arg = $this->arg('tag'); + $tagger = common_canonical_nickname($tagger_arg); + $tag = common_canonical_tag($tag_arg); + + // Permanent redirect on non-canonical nickname + + if ($tagger_arg != $tagger || $tag_arg != $tag) { + $args = array('tagger' => $nickname, 'tag' => $tag); + if ($this->page != 1) { + $args['page'] = $this->page; + } + common_redirect(common_local_url('peopletagged', $args), 301); + return false; + } + + if (!$tagger) { + $this->clientError(_('No tagger.'), 404); + return false; + } + + $user = User::staticGet('nickname', $tagger); + + if (!$user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->tagger = $user->getProfile(); + $this->peopletag = Profile_list::pkeyGet(array('tagger' => $user->id, 'tag' => $tag)); + + if (!$this->peopletag) { + $this->clientError(_('No such peopletag.'), 404); + return false; + } + + return true; + } + + function title() + { + if ($this->page == 1) { + return sprintf(_('People tagged %s by %s'), + $this->peopletag->tag, $this->tagger->nickname); + } else { + return sprintf(_('People tagged %s by %s, page %d'), + $this->peopletag->tag, $this->user->nickname, + $this->page); + } + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function showPageNotice() + { + } + + function showLocalNav() + { + $nav = new PeopletagGroupNav($this, $this->peopletag); + $nav->show(); + } + + function showContent() + { + $offset = ($this->page-1) * PROFILES_PER_PAGE; + $limit = PROFILES_PER_PAGE + 1; + + $cnt = 0; + + $subs = $this->peopletag->getTagged($offset, $limit); + + if ($subs) { + $subscriber_list = new PeopletagMemberList($subs, $this->peopletag, $this); + $cnt = $subscriber_list->show(); + } + + $subs->free(); + + $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, + $this->page, 'peopletagged', + array('tagger' => $this->tagger->nickname, + 'tag' => $this->peopletag->tag)); + } +} + +class PeopletagMemberList extends ProfileList +{ + var $peopletag = null; + + function __construct($profile, $peopletag, $action) + { + parent::__construct($profile, $action); + + $this->peopletag = $peopletag; + } + + function newListItem($profile) + { + return new PeopletagMemberListItem($profile, $this->peopletag, $this->action); + } +} + +class PeopletagMemberListItem extends ProfileListItem +{ + var $peopletag = null; + + function __construct($profile, $peopletag, $action) + { + parent::__construct($profile, $action); + + $this->peopletag = $peopletag; + } + + function showFullName() + { + parent::showFullName(); + if ($this->profile->id == $this->peopletag->tagger) { + $this->out->text(' '); + $this->out->element('span', 'role', _('Creator')); + } + } + + function showActions() + { + $this->startActions(); + if (Event::handle('StartProfileListItemActionElements', array($this))) { + $this->showSubscribeButton(); + // TODO: Untag button + Event::handle('EndProfileListItemActionElements', array($this)); + } + $this->endActions(); + } + + function linkAttributes() + { + // tagging people is healthy page-rank flow. + return parent::linkAttributes(); + } + + /** + * Fetch necessary return-to arguments for the profile forms + * to return to this list when they're done. + * + * @return array + */ + protected function returnToArgs() + { + $args = array('action' => 'peopletagged', + 'tag' => $this->peopletag->tag, + 'tagger' => $this->profile->nickname); + $page = $this->out->arg('page'); + if ($page) { + $args['param-page'] = $page; + } + return $args; + } +} diff --git a/actions/peopletagsbyuser.php b/actions/peopletagsbyuser.php new file mode 100644 index 0000000000..3158b715bc --- /dev/null +++ b/actions/peopletagsbyuser.php @@ -0,0 +1,258 @@ +. + * + * @category Personal + * @package StatusNet + * @author Shashi Gowda + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/peopletaglist.php'; + +class PeopletagsbyuserAction extends OwnerDesignAction +{ + var $page = null; + var $tagger = null; + var $tags = null; + + function isReadOnly($args) + { + return true; + } + + function title() + { + if ($this->page == 1) { + if ($this->isOwner()) { + if ($this->arg('private')) { + return _('Private people tags by you'); + } else if ($this->arg('public')) { + return _('Public people tags by you'); + } + return _('People tags by you'); + } + return sprintf(_("People tags by %s"), $this->tagger->nickname); + } else { + return sprintf(_("People tags by %s, page %d"), $this->tagger->nickname, $this->page); + } + } + + function prepare($args) + { + parent::prepare($args); + + if ($this->arg('public') && $this->arg('private')) { + $this->args['public'] = $this->args['private'] = false; + } + + $nickname_arg = $this->arg('nickname'); + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = $this->getSelfUrlArgs(); + if ($this->arg('page') && $this->arg('page') != 1) { + $args['page'] = $this->arg['page']; + } + common_redirect(common_local_url('peopletagsbyuser', $args), 301); + return false; + } + + $this->user = User::staticGet('nickname', $nickname); + + if (!$this->user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->tagger = $this->user->getProfile(); + + if (!$this->tagger) { + $this->serverError(_('User has no profile.')); + return false; + } + + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + + $offset = ($this->page-1) * PEOPLETAGS_PER_PAGE; + $limit = PEOPLETAGS_PER_PAGE + 1; + + $user = common_current_user(); + if ($this->arg('public')) { + $this->tags = $this->tagger->getOwnedTags(false, $offset, $limit); + } else if ($this->arg('private')) { + if (empty($user)) { + $this->clientError(_('Not logged in'), 403); + } + + if ($this->isOwner()) { + $this->tags = $this->tagger->getPrivateTags($offset, $limit); + } else { + $this->clientError(_('You cannot view others\' private people tags'), 403); + } + } else { + $this->tags = $this->tagger->getOwnedTags(common_current_user(), $offset, $limit); + } + return true; + } + + function handle($args) + { + parent::handle($args); + + # Post from the tag dropdown; redirect to a GET + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + common_redirect(common_local_url('peopletagsbyuser', $this->getSelfUrlArgs()), 303); + return; + } + + $this->showPage(); + } + + function showModeSelector() + { + $this->elementStart('dl', array('id'=>'filter_tags')); + $this->element('dt', null, _('Mode')); + $this->elementStart('dd'); + $this->elementStart('ul'); + $this->elementStart('li', array('id' => 'filter_tags_for', + 'class' => 'child_1')); + $this->element('a', + array('href' => + common_local_url('peopletagsforuser', + array('nickname' => $this->user->nickname))), + sprintf(_('People tags for %s'), $this->tagger->nickname)); + $this->elementEnd('li'); + + if ($this->isOwner()) { + $this->elementStart('li', array('id'=>'filter_tags_item')); + $this->elementStart('form', array('name' => 'modeselector', + 'id' => 'form_filter_bymode', + 'action' => common_local_url('peopletagsbyuser', + array('nickname' => $this->tagger->nickname)), + 'method' => 'post')); + $this->elementStart('fieldset'); + $this->element('legend', null, _('Select tag to filter')); + + $priv = $this->arg('private'); + $pub = $this->arg('public'); + + if (!$priv && !$pub) { + $priv = $pub = true; + } + $this->checkbox('private', _m('Private'), $priv, + _m('Show private tags')); + $this->checkbox('public', _m('Public'), $pub, + _m('Show public tags')); + $this->hidden('nickname', $this->user->nickname); + $this->submit('submit', _('Go')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + $this->elementEnd('li'); + } + $this->elementEnd('ul'); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + } + function showLocalNav() + { + $nav = new PersonalGroupNav($this); + $nav->show(); + } + + function showAnonymousMessage() + { + $notice = + sprintf(_('These are people tags created by **%s**. ' . + 'People tags are how you sort similar ' . + 'people on %%%%site.name%%%%, a [micro-blogging]' . + '(http://en.wikipedia.org/wiki/Micro-blogging) service ' . + 'based on the Free Software [StatusNet](http://status.net/) tool. ' . + 'You can easily keep track of what they ' . + 'are doing by subscribing to the tag\'s timeline.' ), $this->tagger->nickname); + $this->elementStart('div', array('id' => 'anon_notice')); + $this->raw(common_markup_to_html($notice)); + $this->elementEnd('div'); + } + + function showPageNotice() + { + $this->elementStart('div', 'instructions'); + $this->showModeSelector(); + $this->elementEnd('div'); + } + + function showContent() + { + #TODO: controls here. + + $pl = new PeopletagList($this->tags, $this); + $cnt = $pl->show(); + + if ($cnt == 0) { + $this->showEmptyListMessage(); + } + $this->pagination($this->page > 1, $cnt > PEOPLETAGS_PER_PAGE, + $this->page, 'peopletagsbyuser', $this->getSelfUrlArgs()); + } + + function getSelfUrlArgs() + { + $args = array(); + if ($this->arg('private')) { + $args['private'] = 1; + } else if ($this->arg('public')) { + $args['public'] = 1; + } + $args['nickname'] = $this->trimmed('nickname'); + + return $args; + } + + function isOwner() + { + $user = common_current_user(); + return !empty($user) && $user->id == $this->tagger->id; + } + + function showEmptyListMessage() + { + $message = sprintf(_('%s has not created any [people tags](%%%%doc.tags%%%%) yet.'), $this->tagger->nickname); + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + function showSections() + { + #TODO: tags with most subscribers + #TODO: tags with most "members" + } +} diff --git a/actions/peopletagsforuser.php b/actions/peopletagsforuser.php new file mode 100644 index 0000000000..b6875b300b --- /dev/null +++ b/actions/peopletagsforuser.php @@ -0,0 +1,170 @@ +. + * + * @category Personal + * @package StatusNet + * @author Shashi Gowda + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/peopletaglist.php'; + +class PeopletagsforuserAction extends OwnerDesignAction +{ + var $page = null; + var $tagged = null; + + function isReadOnly($args) + { + return true; + } + + function title() + { + if ($this->page == 1) { + return sprintf(_("People tags for %s"), $this->tagged->nickname); + } else { + return sprintf(_("People tags for %s, page %d"), $this->tagged->nickname, $this->page); + } + } + + function prepare($args) + { + parent::prepare($args); + + $nickname_arg = $this->arg('nickname'); + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + if ($this->arg('page') && $this->arg('page') != 1) { + $args['page'] = $this->arg['page']; + } + common_redirect(common_local_url('peopletagsforuser', $args), 301); + return false; + } + + $this->user = User::staticGet('nickname', $nickname); + + if (!$this->user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->tagged = $this->user->getProfile(); + + if (!$this->tagged) { + $this->serverError(_('User has no profile.')); + return false; + } + + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + return true; + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function showLocalNav() + { + $nav = new PersonalGroupNav($this); + $nav->show(); + } + + function showAnonymousMessage() + { + $notice = + sprintf(_('These are people tags for **%s**. ' . + 'People tags are how you sort similar ' . + 'people on %%%%site.name%%%%, a [micro-blogging]' . + '(http://en.wikipedia.org/wiki/Micro-blogging) service ' . + 'based on the Free Software [StatusNet](http://status.net/) tool. ' . + 'You can easily keep track of what they ' . + 'are doing by subscribing to the tag\'s timeline.' ), $this->tagged->nickname); + $this->elementStart('div', array('id' => 'anon_notice')); + $this->raw(common_markup_to_html($notice)); + $this->elementEnd('div'); + } + + function showPageNotice() + { + $this->elementStart('dl', 'filter_tags'); + $this->elementStart('dd', array('id' => 'filter_tags_for', + 'class' => 'child_1')); + + $user = common_current_user(); + $text = ($this->tagged->id == @$user->id) ? _('People tags by you') : + sprintf(_('People tags by %s'), $this->tagged->nickname); + $this->element('a', + array('href' => + common_local_url('peopletagsbyuser', + array('nickname' => $this->tagged->nickname))), + $text); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + } + + + function showContent() + { + #TODO: controls here. + + $offset = ($this->page-1) * PEOPLETAGS_PER_PAGE; + $limit = PEOPLETAGS_PER_PAGE + 1; + + $ptags = $this->tagged->getOtherTags(common_current_user(), $offset, $limit); + + $pl = new PeopletagList($ptags, $this); + $cnt = $pl->show(); + + if ($cnt == 0) { + $this->showEmptyListMessage(); + } + $this->pagination($this->page > 1, $cnt > PEOPLETAGS_PER_PAGE, + $this->page, 'peopletagsforuser', array('nickname' => $this->tagged->id)); + } + + function showEmptyListMessage() + { + $message = sprintf(_('%s has not been [tagged](%%%%doc.tags%%%%) by anyone yet.'), $this->tagged->nickname); + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + function showSections() + { + #TODO: tags with most subscribers + #TODO: tags with most "members" + } +} diff --git a/actions/peopletagsubscribers.php b/actions/peopletagsubscribers.php new file mode 100644 index 0000000000..1f2feeca29 --- /dev/null +++ b/actions/peopletagsubscribers.php @@ -0,0 +1,238 @@ +. + * + * @category Group + * @package StatusNet + * @author Evan Prodromou + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once(INSTALLDIR.'/lib/profilelist.php'); + +/** + * List of peopletag subscribers + * + * @category Peopletag + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class PeopletagsubscribersAction extends OwnerDesignAction +{ + var $page = null; + var $peopletag = null; + var $tagger = null; + + function isReadOnly($args) + { + return true; + } + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + $tagger_arg = $this->arg('tagger'); + $tag_arg = $this->arg('tag'); + $tagger = common_canonical_nickname($tagger_arg); + $tag = common_canonical_tag($tag_arg); + + // Permanent redirect on non-canonical nickname + + if ($tagger_arg != $tagger || $tag_arg != $tag) { + $args = array('tagger' => $nickname, 'tag' => $tag); + if ($this->page != 1) { + $args['page'] = $this->page; + } + common_redirect(common_local_url('peopletagged', $args), 301); + return false; + } + + if (!$tagger) { + $this->clientError(_('No tagger.'), 404); + return false; + } + + $user = User::staticGet('nickname', $tagger); + + if (!$user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->tagger = $user->getProfile(); + $this->peopletag = Profile_list::pkeyGet(array('tagger' => $user->id, 'tag' => $tag)); + + if (!$this->peopletag) { + $this->clientError(_('No such peopletag.'), 404); + return false; + } + + return true; + } + + function title() + { + if ($this->page == 1) { + return sprintf(_('Subscribers of people tagged %s by %s'), + $this->peopletag->tag, $this->tagger->nickname); + } else { + return sprintf(_('Subscribers of people tagged %s by %s, page %d'), + $this->peopletag->tag, $this->tagger->nickname, + $this->page); + } + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function showPageNotice() + { + } + + function showLocalNav() + { + $nav = new PeopletagGroupNav($this); + $nav->show(); + } + + function showContent() + { + $offset = ($this->page-1) * PROFILES_PER_PAGE; + $limit = PROFILES_PER_PAGE + 1; + + $cnt = 0; + + $subs = $this->peopletag->getSubscribers($offset, $limit); + + if ($subs) { + $subscriber_list = new PeopletagSubscriberList($subs, $this->peopletag, $this); + $cnt = $subscriber_list->show(); + } + + $subs->free(); + + $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, + $this->page, 'peopletagsubscribers', + array('tagger' => $this->tagger->nickname, + 'tag' => $this->peopletag->tag)); + } +} + +class PeopletagSubscriberList extends ProfileList +{ + var $peopletag = null; + + function __construct($profile, $peopletag, $action) + { + parent::__construct($profile, $action); + + $this->peopletag = $peopletag; + } + + function newListItem($profile) + { + return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action); + } +} + +class PeopletagSubscriberListItem extends ProfileListItem +{ + var $peopletag = null; + + function __construct($profile, $peopletag, $action) + { + parent::__construct($profile, $action); + + $this->peopletag = $peopletag; + } + + function showFullName() + { + parent::showFullName(); + if ($this->profile->id == $this->peopletag->tagger) { + $this->out->text(' '); + $this->out->element('span', 'role', _('Creator')); + } + } + + function showActions() + { + $this->startActions(); + if (Event::handle('StartProfileListItemActionElements', array($this))) { + $this->showSubscribeButton(); + Event::handle('EndProfileListItemActionElements', array($this)); + } + $this->endActions(); + } + + function linkAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'members')) { + $aAttrs['rel'] .= ' nofollow'; + } + + return $aAttrs; + } + + function homepageAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'members')) { + $aAttrs['rel'] = 'nofollow'; + } + + return $aAttrs; + } + + /** + * Fetch necessary return-to arguments for the profile forms + * to return to this list when they're done. + * + * @return array + */ + protected function returnToArgs() + { + $args = array('action' => 'peopletagsubscribers', + 'tag' => $this->peopletag->tag, + 'tagger' => $this->profile->nickname); + $page = $this->out->arg('page'); + if ($page) { + $args['param-page'] = $page; + } + return $args; + } +} diff --git a/actions/peopletagsubscriptions.php b/actions/peopletagsubscriptions.php new file mode 100644 index 0000000000..b45651d1a2 --- /dev/null +++ b/actions/peopletagsubscriptions.php @@ -0,0 +1,138 @@ +. + * + * @category Personal + * @package StatusNet + * @author Shashi Gowda + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/peopletaglist.php'; + +class PeopletagsubscriptionsAction extends OwnerDesignAction +{ + var $page = null; + var $profile = null; + + function isReadOnly($args) + { + return true; + } + + function title() + { + if ($this->page == 1) { + return sprintf(_("People tags subscriptions by %s"), $this->profile->nickname); + } else { + return sprintf(_("People tags subscriptions by %s, page %d"), $this->profile->nickname, $this->page); + } + } + + function prepare($args) + { + parent::prepare($args); + + $nickname_arg = $this->arg('nickname'); + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + if ($this->arg('page') && $this->arg('page') != 1) { + $args['page'] = $this->arg['page']; + } + common_redirect(common_local_url('peopletagsbyuser', $args), 301); + return false; + } + + $user = User::staticGet('nickname', $nickname); + + if (!$user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->profile = $user->getProfile(); + + if (!$this->profile) { + $this->serverError(_('User has no profile.')); + return false; + } + + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + return true; + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function showLocalNav() + { + $nav = new PersonalGroupNav($this); + $nav->show(); + } + + function showAnonymousMessage() + { + $notice = + sprintf(_('These are people tags subscribed to by **%s**. ' . + 'People tags are how you sort similar ' . + 'people on %%%%site.name%%%%, a [micro-blogging]' . + '(http://en.wikipedia.org/wiki/Micro-blogging) service ' . + 'based on the Free Software [StatusNet](http://status.net/) tool. ' . + 'You can easily keep track of what they ' . + 'are doing by subscribing to the tag\'s timeline.' ), $this->profile->nickname); + $this->elementStart('div', array('id' => 'anon_notice')); + $this->raw(common_markup_to_html($notice)); + $this->elementEnd('div'); + } + + function showContent() + { + $offset = ($this->page-1) * PEOPLETAGS_PER_PAGE; + $limit = PEOPLETAGS_PER_PAGE + 1; + + $ptags = $this->profile->getTagSubscriptions($offset, $limit); + + $pl = new PeopletagList($ptags, $this); + $cnt = $pl->show(); + + $this->pagination($this->page > 1, $cnt > PEOPLETAGS_PER_PAGE, + $this->page, 'peopletagsubscriptions', array('nickname' => $this->profile->id)); + } + + function showSections() + { + #TODO: tags with most subscribers + #TODO: tags with most "members" + } +} diff --git a/actions/profilecompletion.php b/actions/profilecompletion.php new file mode 100644 index 0000000000..8208f3bd37 --- /dev/null +++ b/actions/profilecompletion.php @@ -0,0 +1,214 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/peopletageditform.php'; + +/** + * Subscription action + * + * Subscribing to a profile. Does not work for OMB 0.1 remote subscriptions, + * but may work for other remote subscription protocols, like OStatus. + * + * Takes parameters: + * + * - subscribeto: a profile ID + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class ProfilecompletionAction extends Action +{ + var $user; + var $peopletag; + var $field; + var $msg; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + + function prepare($args) + { + parent::prepare($args); + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_('Not logged in.')); + return false; + } + + $id = $this->arg('peopletag_id'); + $this->peopletag = Profile_list::staticGet('id', $id); + + if (empty($this->peopletag)) { + $this->clientError(_('No such peopletag.')); + return false; + } + + $field = $this->arg('field'); + if (!in_array($field, array('fulltext', 'nickname', 'fullname', 'description', 'location', 'uri'))) { + $this->clientError(sprintf(_('Unidentified field %s'), htmlspecialchars($field)), 404); + return false; + } + $this->field = $field; + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + + function handle($args) + { + $this->msg = null; + + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _('Search results')); + $this->elementEnd('head'); + $this->elementStart('body'); + $profiles = $this->getResults(); + + if ($this->msg !== null) { + $this->element('p', 'error', $this->msg); + } else { + if (count($profiles) > 0) { + $this->elementStart('ul', array('id' => 'profile_search_results', 'class' => 'profile-lister')); + foreach ($profiles as $profile) { + $this->showProfileItem($profile); + } + $this->elementEnd('ul'); + } else { + $this->element('p', 'error', _('No results.')); + } + } + $this->elementEnd('body'); + $this->elementEnd('html'); + } + + function getResults() + { + $profiles = array(); + $q = $this->arg('q'); + $q = strtolower($q); + if (strlen($q) < 3) { + $this->msg = _('The search string must be atleast 3 characters long'); + } + $page = $this->arg('page'); + $page = (int) (empty($page) ? 1 : $page); + + $profile = new Profile(); + $search_engine = $profile->getSearchEngine('profile'); + + if (Event::handle('StartProfileCompletionSearch', array($this, &$profile, $search_engine))) { + $search_engine->set_sort_mode('chron'); + $search_engine->limit((($page-1)*PROFILES_PER_PAGE), PROFILES_PER_PAGE + 1); + + if (false === $search_engine->query($q)) { + $cnt = 0; + } + else { + $cnt = $profile->find(); + } + Event::handle('EndProfileCompletionSearch', $this, &$profile, $search_engine); + } + + while ($profile->fetch()) { + $profiles[] = clone($profile); + } + return $this->filter($profiles); + } + + function filter($profiles) + { + $current = $this->user->getProfile(); + $filtered_profiles = array(); + foreach ($profiles as $profile) { + if ($current->canTag($profile)) { + $filtered_profiles[] = $profile; + } + } + return $filtered_profiles; + } + + function showProfileItem($profile) + { + $this->elementStart('li', 'entity_removable_profile'); + $item = new TaggedProfileItem($this, $profile); + $item->show(); + $this->elementStart('span', 'entity_actions'); + + if ($profile->isTagged($this->peopletag)) { + $untag = new UntagButton($this, $profile, $this->peopletag); + $untag->show(); + } else { + $tag = new TagButton($this, $profile, $this->peopletag); + $tag->show(); + } + + $this->elementEnd('span'); + $this->elementEnd('li'); + } +} diff --git a/actions/profilesettings.php b/actions/profilesettings.php index e1d686ca29..046bedf8ac 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -279,18 +279,24 @@ class ProfilesettingsAction extends SettingsAction return; } - if ($tagstring) { - $tags = array_map('common_canonical_tag', preg_split('/[\s,]+/', $tagstring)); - } else { - $tags = array(); - } + $tags = array(); + $tag_priv = array(); + if (is_string($tagstring) && strlen($tagstring) > 0) { - foreach ($tags as $tag) { - if (!common_valid_profile_tag($tag)) { - // TRANS: Validation error in form for profile settings. - // TRANS: %s is an invalid tag. - $this->showForm(sprintf(_('Invalid tag: "%s".'), $tag)); - return; + $tags = preg_split('/[\s,]+/', $tagstring); + + foreach ($tags as &$tag) { + $private = @$tag[0] === '.'; + + $tag = common_canonical_tag($tag); + if (!common_valid_profile_tag($tag)) { + // TRANS: Validation error in form for profile settings. + // TRANS: %s is an invalid tag. + $this->showForm(sprintf(_('Invalid tag: "%s"'), $tag)); + return; + } + + $tag_priv[$tag] = $private; } } @@ -421,7 +427,7 @@ class ProfilesettingsAction extends SettingsAction } // Set the user tags - $result = $user->setSelfTags($tags); + $result = $user->setSelfTags($tags, $tag_priv); if (!$result) { // TRANS: Server error thrown when user profile settings tags could not be saved. diff --git a/actions/profiletagbyid.php b/actions/profiletagbyid.php new file mode 100644 index 0000000000..7de5737e9a --- /dev/null +++ b/actions/profiletagbyid.php @@ -0,0 +1,92 @@ +. + * + * @category Peopletag + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class ProfiletagbyidAction extends Action +{ + /** peopletag we're viewing. */ + var $peopletag = null; + + /** + * Is this page read-only? + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + function prepare($args) + { + parent::prepare($args); + + $id = $this->arg('id'); + $tagger_id = $this->arg('tagger_id'); + + if (!$id) { + $this->clientError(_('No ID.')); + return false; + } + + common_debug("Peopletag id $id by user id $tagger_id"); + + $this->peopletag = Profile_list::staticGet('id', $id); + + if (!$this->peopletag) { + $this->clientError(_('No such people tag.'), 404); + return false; + } + + $user = User::staticGet('id', $tagger_id); + if (!$user) { + // remote peopletag, permanently redirect + common_redirect($this->peopletag->permalink(), 301); + } + + return true; + } + + /** + * Handle the request + * + * Shows a profile for the group, some controls, and a list of + * group notices. + * + * @return void + */ + + function handle($args) + { + common_redirect($this->peopletag->homeUrl(), 303); + } +} diff --git a/actions/public.php b/actions/public.php index 727c76d528..ca9af4601e 100644 --- a/actions/public.php +++ b/actions/public.php @@ -232,6 +232,8 @@ class PublicAction extends Action $pop->show(); $gbp = new GroupsByMembersSection($this); $gbp->show(); + $ptp = new PeopletagsBySubsSection($this); + $ptp->show(); $feat = new FeaturedUsersSection($this); $feat->show(); } diff --git a/actions/publicpeopletagcloud.php b/actions/publicpeopletagcloud.php new file mode 100644 index 0000000000..dc252fe4c8 --- /dev/null +++ b/actions/publicpeopletagcloud.php @@ -0,0 +1,173 @@ +. + * + * @category Public + * @package StatusNet + * @author Mike Cochrane + * @author Evan Prodromou + * @copyright 2008 Mike Cochrane + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +define('TAGS_PER_PAGE', 100); + +/** + * Public tag cloud for notices + * + * @category Personal + * @package StatusNet + * @author Mike Cochrane + * @author Evan Prodromou + * @copyright 2008 Mike Cochrane + * @copyright 2008-2009 StatusNet, Inc. + * @link http://status.net/ + */ + +class PublicpeopletagcloudAction extends Action +{ + function isReadOnly($args) + { + return true; + } + + function title() + { + return _('Public people tag cloud'); + } + + function showPageNotice() + { + $this->element('p', 'instructions', + sprintf(_('These are most used people tags on %s '), + common_config('site', 'name'))); + } + + function showEmptyList() + { + $message = _('No one has [tagged](%%doc.tags%%) anyone yet.') . ' '; + + if (common_logged_in()) { + $message .= _('Be the first to tag someone!'); + } + else { + $message .= _('Why not [register an account](%%action.register%%) and be the first to tag someone!'); + } + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + function showLocalNav() + { + $nav = new PublicGroupNav($this); + $nav->show(); + } + + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + function showContent() + { + // XXX: cache this + + $tags = new Profile_tag(); + $plist = new Profile_list(); + $plist->private = false; + + $tags->joinAdd($plist); + $tags->selectAdd(); + $tags->selectAdd('profile_tag.tag'); + $tags->selectAdd('count(profile_tag.tag) as weight'); + $tags->groupBy('profile_tag.tag'); + $tags->orderBy('weight DESC'); + + $tags->limit(TAGS_PER_PAGE); + + $cnt = $tags->find(); + + if ($cnt > 0) { + $this->elementStart('div', array('id' => 'tagcloud', + 'class' => 'section')); + + $tw = array(); + $sum = 0; + while ($tags->fetch()) { + $tw[$tags->tag] = $tags->weight; + $sum += $tags->weight; + } + + ksort($tw); + + $this->elementStart('dl'); + $this->element('dt', null, _('People tag cloud')); + $this->elementStart('dd'); + $this->elementStart('ul', 'tags xoxo tag-cloud'); + foreach ($tw as $tag => $weight) { + if ($sum) { + $weightedSum = $weight/$sum; + } else { + $weightedSum = 0.5; + } + $this->showTag($tag, $weight, $weightedSum); + } + $this->elementEnd('ul'); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + $this->elementEnd('div'); + } else { + $this->showEmptyList(); + } + } + + function showTag($tag, $weight, $relative) + { + if ($relative > 0.1) { + $rel = 'tag-cloud-7'; + } else if ($relative > 0.05) { + $rel = 'tag-cloud-6'; + } else if ($relative > 0.02) { + $rel = 'tag-cloud-5'; + } else if ($relative > 0.01) { + $rel = 'tag-cloud-4'; + } else if ($relative > 0.005) { + $rel = 'tag-cloud-3'; + } else if ($relative > 0.002) { + $rel = 'tag-cloud-2'; + } else { + $rel = 'tag-cloud-1'; + } + + $this->elementStart('li', $rel); + + $count = ($weight == 1) ? '1 person tagged' : '%d people tagged'; + $this->element('a', array('href' => common_local_url('peopletag', array('tag' => $tag)), + 'title' => sprintf(_($count), $weight)), $tag); + $this->elementEnd('li'); + } +} diff --git a/actions/removepeopletag.php b/actions/removepeopletag.php new file mode 100644 index 0000000000..aa8ae2b6ad --- /dev/null +++ b/actions/removepeopletag.php @@ -0,0 +1,173 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/togglepeopletag.php'; + +/** + * Subscription action + * + * Subscribing to a profile. Does not work for OMB 0.1 remote subscriptions, + * but may work for other remote subscription protocols, like OStatus. + * + * Takes parameters: + * + * - subscribeto: a profile ID + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class RemovepeopletagAction extends Action +{ + var $user; + var $tagged; + var $peopletag; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + + function prepare($args) + { + parent::prepare($args); + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_('Not logged in.')); + return false; + } + + // Profile to subscribe to + + $tagged_id = $this->arg('tagged'); + + $this->tagged = Profile::staticGet('id', $tagged_id); + + if (empty($this->tagged)) { + $this->clientError(_('No such profile.')); + return false; + } + + $id = $this->arg('peopletag_id'); + $this->peopletag = Profile_list::staticGet('id', $id); + + if (empty($this->peopletag)) { + $this->clientError(_('No such peopletag.')); + return false; + } + + // OMB 0.1 doesn't have a mechanism for local-server- + // originated tag. + + $omb01 = Remote_profile::staticGet('id', $tagged_id); + + if (!empty($omb01)) { + $this->clientError(_('You cannot tag or untag an OMB 0.1'. + ' remote profile with this action.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + + function handle($args) + { + // Throws exception on error + + $ptag = Profile_tag::unTag($this->user->id, $this->tagged->id, + $this->peopletag->tag); + + if (!$ptag) { + $user = User::staticGet('id', $this->tagged->id); + if ($user) { + $this->clientError( + sprintf(_('There was an unexpected error while tagging %s'), + $user->nickname)); + } else { + $this->clientError(sprintf(_('There was a problem tagging %s.' . + 'The remote server is probably not responding correctly, ' . + 'please try retrying later.'), $this->profile->profileurl)); + } + return false; + } + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _('Untagged')); + $this->elementEnd('head'); + $this->elementStart('body'); + $unsubscribe = new TagButton($this, $this->tagged, $this->peopletag); + $unsubscribe->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('subscriptions', + array('nickname' => $this->user->nickname)); + common_redirect($url, 303); + } + } +} diff --git a/actions/selftag.php b/actions/selftag.php new file mode 100644 index 0000000000..0efb896f6b --- /dev/null +++ b/actions/selftag.php @@ -0,0 +1,204 @@ +. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * This class outputs a paginated list of profiles self-tagged with a given tag + * + * @category Output + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see Action + */ + +class SelftagAction extends Action +{ + + var $tag = null; + var $page = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + function prepare($argarray) + { + parent::prepare($argarray); + + $this->tag = $this->trimmed('tag'); + + if (!common_valid_profile_tag($this->tag)) { + $this->clientError(sprintf(_('Not a valid people tag: %s.'), + $this->tag)); + return; + } + + $this->page = ($this->arg('page')) ? $this->arg('page') : 1; + + common_set_returnto($this->selfUrl()); + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return boolean is read only action? + */ + function handle($argarray) + { + parent::handle($argarray); + $this->showPage(); + } + + /** + * Whips up a query to get a list of profiles based on the provided + * people tag and page, initalizes a ProfileList widget, and displays + * it to the user. + * + * @return nothing + */ + function showContent() + { + + $profile = new Profile(); + + $offset = ($this->page - 1) * PROFILES_PER_PAGE; + $limit = PROFILES_PER_PAGE + 1; + + if (common_config('db', 'type') == 'pgsql') { + $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $lim = ' LIMIT ' . $offset . ', ' . $limit; + } + + // XXX: memcached this + + $qry = 'SELECT profile.* ' . + 'FROM profile JOIN ( profile_tag, profile_list ) ' . + 'ON profile.id = profile_tag.tagger ' . + 'AND profile_tag.tagger = profile_list.tagger ' . + 'AND profile_list.tag = profile_tag.tag ' . + 'WHERE profile_tag.tagger = profile_tag.tagged ' . + "AND profile_tag.tag = '%s' "; + + $user = common_current_user(); + if (empty($user)) { + $qry .= 'AND profile_list.private = false '; + } else { + $qry .= 'AND (profile_list.tagger = ' . $user->id . + ' OR profile_list.private = false) '; + } + + $qry .= 'ORDER BY profile_tag.modified DESC%s'; + + $profile->query(sprintf($qry, $this->tag, $lim)); + + $ptl = new SelfTagProfileList($profile, $this); // pass the ammunition + $cnt = $ptl->show(); + + $this->pagination($this->page > 1, + $cnt > PROFILES_PER_PAGE, + $this->page, + 'selftag', + array('tag' => $this->tag)); + } + + /** + * Returns the page title + * + * @return string page title + */ + function title() + { + return sprintf(_('Users self-tagged with %1$s - page %2$d'), + $this->tag, $this->page); + } + +} + +class SelfTagProfileList extends ProfileList +{ + function newListItem($profile) + { + return new SelfTagProfileListItem($profile, $this->action); + } +} + +class SelfTagProfileListItem extends ProfileListItem +{ + function linkAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'selftag')) { + $aAttrs['rel'] .= ' nofollow'; + } + + return $aAttrs; + } + + function homepageAttributes() + { + $aAttrs = parent::linkAttributes(); + + if (common_config('nofollow', 'selftag')) { + $aAttrs['rel'] = 'nofollow'; + } + + return $aAttrs; + } + + function showTags() + { + $selftags = new SelfTagsWidget($this->out, $this->profile, $this->profile); + $selftags->show(); + + $user = common_current_user(); + + if (!empty($user) && $user->id != $this->profile->id && + $user->getProfile()->canTag($this->profile)) { + $yourtags = new PeopleTagsWidget($this->out, $user, $this->profile); + $yourtags->show(); + } + } +} diff --git a/actions/showprofiletag.php b/actions/showprofiletag.php new file mode 100644 index 0000000000..a4cace6e96 --- /dev/null +++ b/actions/showprofiletag.php @@ -0,0 +1,354 @@ +. + * + * @category Actions + * @package Actions + * @license GNU Affero General Public License http://www.gnu.org/licenses/ + * @link http://status.net + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/profileminilist.php'; +require_once INSTALLDIR.'/lib/peopletaglist.php'; +require_once INSTALLDIR.'/lib/noticelist.php'; +require_once INSTALLDIR.'/lib/feedlist.php'; + +class ShowprofiletagAction extends Action +{ + var $notice, $tagger, $peopletag; + + function isReadOnly($args) + { + return true; + } + + function prepare($args) + { + parent::prepare($args); + + $tagger_arg = $this->arg('tagger'); + $tag_arg = $this->arg('tag'); + $tagger = common_canonical_nickname($tagger_arg); + $tag = common_canonical_tag($tag_arg); + + // Permanent redirect on non-canonical nickname + + if ($tagger_arg != $tagger || $tag_arg != $tag) { + $args = array('tagger' => $nickname, 'tag' => $tag); + if ($this->page != 1) { + $args['page'] = $this->page; + } + common_redirect(common_local_url('showprofiletag', $args), 301); + return false; + } + + if (!$tagger) { + $this->clientError(_('No tagger.'), 404); + return false; + } + + $user = User::staticGet('nickname', $tagger); + + if (!$user) { + $this->clientError(_('No such user.'), 404); + return false; + } + + $this->tagger = $user->getProfile(); + $this->peopletag = Profile_list::pkeyGet(array('tagger' => $user->id, 'tag' => $tag)); + + $current = common_current_user(); + $can_see = !empty($this->peopletag) && (!$this->peopletag->private || + ($this->peopletag->private && $this->peopletag->tagger === $current->id)); + + if (!$can_see) { + $this->clientError(_('No such peopletag.'), 404); + return false; + } + + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + $this->notice = $this->peopletag->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); + + if ($this->page > 1 && $this->notice->N == 0) { + // TRANS: Server error when page not found (404) + $this->serverError(_('No such page.'), $code = 404); + } + + return true; + } + + function handle($args) + { + parent::handle($args); + + if (!$this->peopletag) { + $this->clientError(_('No such user.')); + return; + } + + $this->showPage(); + } + + function title() + { + if ($this->page > 1) { + + if($this->peopletag->private) { + return sprintf(_('Private timeline for people tagged %s by you, page %d'), + $this->peopletag->tag, $this->page); + } + + $current = common_current_user(); + if (!empty($current) && $current->id == $this->peopletag->tagger) { + return sprintf(_('Timeline for people tagged %s by you, page %d'), + $this->peopletag->tag, $this->page); + } + + // TRANS: Page title. %1$s is user nickname, %2$d is page number + return sprintf(_('Timeline for people tagged %1$s by %2$s, page %3$d'), + $this->peopletag->tag, + $this->tagger->nickname, + $this->page + ); + } else { + + if($this->peopletag->private) { + return sprintf(_('Private timeline of people tagged %s by you'), + $this->peopletag->tag, $this->page); + } + + $current = common_current_user(); + if (!empty($current) && $current->id == $this->peopletag->tagger) { + return sprintf(_('Timeline for people tagged %s by you'), + $this->peopletag->tag, $this->page); + } + + // TRANS: Page title. %1$s is user nickname, %2$d is page number + return sprintf(_('Timeline for people tagged %1$s by %2$s'), + $this->peopletag->tag, + $this->tagger->nickname, + $this->page + ); + } + } + + function getFeeds() + { + #XXX: make these actually work + return array(new Feed(Feed::RSS2, + common_local_url( + 'ApiTimelineList', array( + 'user' => $this->tagger->id, + 'id' => $this->peopletag->id, + 'format' => 'rss' + ) + ), + // TRANS: %1$s is user nickname + sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->tagger->nickname)), + new Feed(Feed::ATOM, + common_local_url( + 'ApiTimelineList', array( + 'user' => $this->tagger->id, + 'id' => $this->peopletag->id, + 'format' => 'atom' + ) + ), + // TRANS: %1$s is user nickname + sprintf(_('Feed for people tagged %s by %s (Atom)'), + $this->peopletag->tag, $this->tagger->nickname + ) + ) + ); + } + + function showLocalNav() + { + $nav = new PeopletagGroupNav($this); + $nav->show(); + } + + function showEmptyListMessage() + { + // TRANS: %1$s is user nickname + $message = sprintf(_('This is the timeline for people tagged %s by %s but no one has posted anything yet.'), $this->peopletag->tag, $this->tagger->nickname) . ' '; + + if (common_logged_in()) { + $current_user = common_current_user(); + if ($this->tagger->id == $current_user->id) { + $message .= _('Try tagging more people.'); + } + } else { + $message .= _('Why not [register an account](%%%%action.register%%%%) and start following this timeline.'); + } + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + function showContent() + { + $this->showPeopletag(); + $this->showNotices(); + } + + function showPeopletag() + { + $cur = common_current_user(); + $tag = new Peopletag($this->peopletag, $cur, $this); + $tag->show(); + } + + function showNotices() + { + if (Event::handle('StartShowProfileTagContent', array($this))) { + $nl = new NoticeList($this->notice, $this); + + $cnt = $nl->show(); + + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + + $this->pagination( + $this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'showprofiletag', array('tag' => $this->peopletag->tag, + 'tagger' => $this->tagger->nickname) + ); + + Event::handle('EndShowProfileTagContent', array($this)); + } + } + + function showSections() + { + $this->showTagged(); + if (!$this->peopletag->private) { + $this->showSubscribers(); + } + # $this->showStatistics(); + } + + function showPageTitle() + { + $this->element('h1', null, $this->title()); + } + + function showTagged() + { + $profile = $this->peopletag->getTagged(0, PROFILES_PER_MINILIST + 1); + + $this->elementStart('div', array('id' => 'entity_tagged', + 'class' => 'section')); + if (Event::handle('StartShowTaggedProfilesMiniList', array($this))) { + + $title = ''; + + $current = common_current_user(); + if(!empty($current) && $this->peopletag->tagger == $current->id) { + $title = sprintf(_('People tagged %s by you'), $this->peopletag->tag); + } else { + $title = sprintf(_('People tagged %1$s by %2$s'), + $this->peopletag->tag, + $this->tagger->nickname); + } + + $this->element('h2', null, $title); + + $cnt = 0; + + if (!empty($profile)) { + $pml = new ProfileMiniList($profile, $this); + $cnt = $pml->show(); + if ($cnt == 0) { + $this->element('p', null, _('(None)')); + } + } + + if ($cnt > PROFILES_PER_MINILIST) { + $this->elementStart('p'); + $this->element('a', array('href' => common_local_url('taggedprofiles', + array('nickname' => $this->tagger->nickname, + 'profiletag' => $this->peopletag->tag)), + 'class' => 'more'), + _('Show all')); + $this->elementEnd('p'); + } + + Event::handle('EndShowTaggedProfilesMiniList', array($this)); + } + $this->elementEnd('div'); + } + + function showSubscribers() + { + $profile = $this->peopletag->getSubscribers(0, PROFILES_PER_MINILIST + 1); + + $this->elementStart('div', array('id' => 'entity_subscribers', + 'class' => 'section')); + if (Event::handle('StartShowProfileTagSubscribersMiniList', array($this))) { + $this->element('h2', null, _('Subscribers')); + + $cnt = 0; + + if (!empty($profile)) { + $pml = new ProfileMiniList($profile, $this); + $cnt = $pml->show(); + if ($cnt == 0) { + $this->element('p', null, _('(None)')); + } + } + + if ($cnt > PROFILES_PER_MINILIST) { + $this->elementStart('p'); + $this->element('a', array('href' => common_local_url('profiletagsubscribers', + array('nickname' => $this->tagger->nickname, + 'profiletag' => $this->peopletag->tag)), + 'class' => 'more'), + _('All subscribers')); + $this->elementEnd('p'); + } + + Event::handle('EndShowProfileTagSubscribersMiniList', array($this)); + } + $this->elementEnd('div'); + } +} + +class Peopletag extends PeopletagListItem +{ + function showStart() + { + $mode = $this->peopletag->private ? 'private' : 'public'; + $this->out->elementStart('div', array('class' => 'hentry peopletag peopletag-profile mode-'.$mode, + 'id' => 'peopletag-' . $this->peopletag->id)); + } + + function showEnd() + { + $this->out->elementEnd('div'); + } + + function showAvatar() + { + parent::showAvatar(AVATAR_PROFILE_SIZE); + } +} diff --git a/actions/subscribepeopletag.php b/actions/subscribepeopletag.php new file mode 100644 index 0000000000..e38ecb2d3e --- /dev/null +++ b/actions/subscribepeopletag.php @@ -0,0 +1,144 @@ +. + * + * @category Peopletag + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Subscribe to a peopletag + * + * This is the action for subscribing to a peopletag. It works more or less like the join action + * for groups. + * + * @category Peopletag + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class SubscribepeopletagAction extends Action +{ + var $peopletag = null; + var $tagger = null; + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to unsubscribe to a peopletag.')); + return false; + } + // Only allow POST requests + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError(_('This action only accepts POST requests.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + $tagger_arg = $this->trimmed('tagger'); + $tag_arg = $this->trimmed('tag'); + + $id = intval($this->arg('id')); + if ($id) { + $this->peopletag = Profile_list::staticGet('id', $id); + } else { + $this->clientError(_('No ID given.'), 404); + return false; + } + + if (!$this->peopletag || $this->peopletag->private) { + $this->clientError(_('No such peopletag.'), 404); + return false; + } + + $this->tagger = Profile::staticGet('id', $this->peopletag->tagger); + + return true; + } + + /** + * Handle the request + * + * On POST, add the current user to the group + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $cur = common_current_user(); + + try { + Profile_tag_subscription::add($this->peopletag, $cur); + } catch (Exception $e) { + $this->serverError(sprintf(_('Could not subscribe user %1$s to peopletag %2$s.'), + $cur->nickname, $this->peopletag->tag) . ' ' . $e->getMessage()); + } + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, sprintf(_('%1$s subscribed to peopletag %2$s by %3$s'), + $cur->nickname, + $this->peopletag->tag, + $this->tagger->nickname)); + $this->elementEnd('head'); + $this->elementStart('body'); + $lf = new UnsubscribePeopletagForm($this, $this->peopletag); + $lf->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + common_redirect(common_local_url('peopletagsubscribers', + array('tagger' => $this->tagger->nickname, + 'tag' =>$this->peopletag->tag)), + 303); + } + } +} diff --git a/actions/tagother.php b/actions/tagother.php deleted file mode 100644 index 258c13bdcc..0000000000 --- a/actions/tagother.php +++ /dev/null @@ -1,220 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/settingsaction.php'); - -class TagotherAction extends Action -{ - var $profile = null; - var $error = null; - - function prepare($args) - { - parent::prepare($args); - if (!common_logged_in()) { - $this->clientError(_('Not logged in.'), 403); - return false; - } - - $id = $this->trimmed('id'); - if (!$id) { - $this->clientError(_('No ID argument.')); - return false; - } - - $this->profile = Profile::staticGet('id', $id); - - if (!$this->profile) { - $this->clientError(_('No profile with that ID.')); - return false; - } - - return true; - } - - function handle($args) - { - parent::handle($args); - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->saveTags(); - } else { - $this->showForm($profile); - } - } - - function title() - { - return sprintf(_('Tag %s'), $this->profile->nickname); - } - - function showForm($error=null) - { - $this->error = $error; - $this->showPage(); - } - - function showContent() - { - $this->elementStart('div', 'entity_profile vcard author'); - $this->element('h2', null, _('User profile')); - - $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); - $this->element('img', array('src' => ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE), - 'class' => 'photo avatar entity_depiction', - 'width' => AVATAR_PROFILE_SIZE, - 'height' => AVATAR_PROFILE_SIZE, - 'alt' => - ($this->profile->fullname) ? $this->profile->fullname : - $this->profile->nickname)); - - $this->element('a', array('href' => $this->profile->profileurl, - 'class' => 'entity_nickname nickname'), - $this->profile->nickname); - - if ($this->profile->fullname) { - $this->element('div', 'fn entity_fn', $this->profile->fullname); - } - - if ($this->profile->location) { - $this->element('div', 'label entity_location', $this->profile->location); - } - - if ($this->profile->homepage) { - $this->element('a', array('href' => $this->profile->homepage, - 'rel' => 'me', - 'class' => 'url entity_url'), - $this->profile->homepage); - } - - if ($this->profile->bio) { - $this->element('div', 'note entity_note', $this->profile->bio); - } - - $this->elementEnd('div'); - - $this->elementStart('form', array('method' => 'post', - 'id' => 'form_tag_user', - 'class' => 'form_settings', - 'name' => 'tagother', - 'action' => common_local_url('tagother', array('id' => $this->profile->id)))); - - $this->elementStart('fieldset'); - $this->element('legend', null, _('Tag user')); - $this->hidden('token', common_session_token()); - $this->hidden('id', $this->profile->id); - - $user = common_current_user(); - - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->input('tags', _('Tags'), - ($this->arg('tags')) ? $this->arg('tags') : implode(' ', Profile_tag::getTags($user->id, $this->profile->id)), - _('Tags for this user (letters, numbers, -, ., and _), comma- or space- separated')); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('save', _('Save')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); - } - - function saveTags() - { - $id = $this->trimmed('id'); - $tagstring = $this->trimmed('tags'); - $token = $this->trimmed('token'); - - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - - if (is_string($tagstring) && strlen($tagstring) > 0) { - - $tags = array_map('common_canonical_tag', - preg_split('/[\s,]+/', $tagstring)); - - foreach ($tags as $tag) { - if (!common_valid_profile_tag($tag)) { - $this->showForm(sprintf(_('Invalid tag: "%s"'), $tag)); - return; - } - } - } else { - $tags = array(); - } - - $user = common_current_user(); - - if (!Subscription::pkeyGet(array('subscriber' => $user->id, - 'subscribed' => $this->profile->id)) && - !Subscription::pkeyGet(array('subscriber' => $this->profile->id, - 'subscribed' => $user->id))) - { - $this->clientError(_('You can only tag people you are subscribed to or who are subscribed to you.')); - return; - } - - $result = Profile_tag::setTags($user->id, $this->profile->id, $tags); - - if (!$result) { - $this->clientError(_('Could not save tags.')); - return; - } - - $action = $user->isSubscribed($this->profile) ? 'subscriptions' : 'subscribers'; - - if ($this->boolean('ajax')) { - $this->startHTML('text/xml;charset=utf-8'); - $this->elementStart('head'); - $this->element('title', null, _('Tags')); - $this->elementEnd('head'); - $this->elementStart('body'); - $this->elementStart('p', 'subtags'); - foreach ($tags as $tag) { - $this->element('a', array('href' => common_local_url($action, - array('nickname' => $user->nickname, - 'tag' => $tag))), - $tag); - } - $this->elementEnd('p'); - $this->elementEnd('body'); - $this->elementEnd('html'); - } else { - common_redirect(common_local_url($action, array('nickname' => - $user->nickname)), - 303); - } - } - - function showPageNotice() - { - if ($this->error) { - $this->element('p', 'error', $this->error); - } else { - $this->elementStart('div', 'instructions'); - $this->element('p', null, - _('Use this form to add tags to your subscribers or subscriptions.')); - $this->elementEnd('div'); - } - } -} - diff --git a/actions/tagprofile.php b/actions/tagprofile.php new file mode 100644 index 0000000000..8c0e039dd6 --- /dev/null +++ b/actions/tagprofile.php @@ -0,0 +1,249 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +require_once INSTALLDIR . '/lib/settingsaction.php'; +require_once INSTALLDIR . '/lib/peopletags.php'; + +class TagprofileAction extends Action +{ + var $profile = null; + var $error = null; + + function prepare($args) + { + parent::prepare($args); + if (!common_logged_in()) { + common_set_returnto($_SERVER['REQUEST_URI']); + if (Event::handle('RedirectToLogin', array($this, null))) { + common_redirect(common_local_url('login'), 303); + } + } + + $id = $this->trimmed('id'); + if (!$id) { + $this->profile = false; + } else { + $this->profile = Profile::staticGet('id', $id); + + if (!$this->profile) { + $this->clientError(_('No profile with that ID.')); + return false; + } + } + + $current = common_current_user()->getProfile(); + if ($this->profile && !$current->canTag($this->profile)) { + $this->clientError(_('You cannot tag this user.')); + } + return true; + } + + function handle($args) + { + parent::handle($args); + if (Event::handle('StartTagProfileAction', array($this, $this->profile))) { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->saveTags(); + } else { + $this->showForm(); + } + Event::handle('EndTagProfileAction', array($this, $this->profile)); + } + } + + function title() + { + if (!$this->profile) { + return _('Tag a profile'); + } + return sprintf(_('Tag %s'), $this->profile->nickname); + } + + function showForm($error=null) + { + $this->error = $error; + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _('Error')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->element('p', 'error', $error); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->showPage(); + } + } + + function showContent() + { + if (Event::handle('StartShowTagProfileForm', array($this, $this->profile)) && $this->profile) { + $this->elementStart('div', 'entity_profile vcard author'); + $this->element('h2', null, _('User profile')); + + $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); + $this->element('img', array('src' => ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE), + 'class' => 'photo avatar entity_depiction', + 'width' => AVATAR_PROFILE_SIZE, + 'height' => AVATAR_PROFILE_SIZE, + 'alt' => + ($this->profile->fullname) ? $this->profile->fullname : + $this->profile->nickname)); + + $this->element('a', array('href' => $this->profile->profileurl, + 'class' => 'entity_nickname nickname'), + $this->profile->nickname); + if ($this->profile->fullname) { + $this->element('div', 'fn entity_fn', $this->profile->fullname); + } + + if ($this->profile->location) { + $this->element('div', 'label entity_location', $this->profile->location); + } + + if ($this->profile->homepage) { + $this->element('a', array('href' => $this->profile->homepage, + 'rel' => 'me', + 'class' => 'url entity_url'), + $this->profile->homepage); + } + + if ($this->profile->bio) { + $this->element('div', 'note entity_note', $this->profile->bio); + } + + $this->elementEnd('div'); + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_tag_user', + 'class' => 'form_settings', + 'name' => 'tagprofile', + 'action' => common_local_url('tagprofile', array('id' => $this->profile->id)))); + + $this->elementStart('fieldset'); + $this->element('legend', null, _('Tag user')); + $this->hidden('token', common_session_token()); + $this->hidden('id', $this->profile->id); + + $user = common_current_user(); + + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + + $tags = Profile_tag::getTagsArray($user->id, $this->profile->id, $user->id); + $this->input('tags', _('Tags'), + ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $tags), + _('Tags for this user (letters, numbers, -, ., and _), comma- or space- separated')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('save', _('Save')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + + Event::handle('EndShowTagProfileForm', array($this, $this->profile)); + } + } + + function saveTags() + { + $id = $this->trimmed('id'); + $tagstring = $this->trimmed('tags'); + $token = $this->trimmed('token'); + + if (Event::handle('StartSavePeopletags', array($this, $tagstring))) { + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + $tags = array(); + $tag_priv = array(); + + if (is_string($tagstring) && strlen($tagstring) > 0) { + + $tags = preg_split('/[\s,]+/', $tagstring); + + foreach ($tags as &$tag) { + $private = @$tag[0] === '.'; + + $tag = common_canonical_tag($tag); + if (!common_valid_profile_tag($tag)) { + $this->showForm(sprintf(_('Invalid tag: "%s"'), $tag)); + return; + } + + $tag_priv[$tag] = $private; + } + } + + $user = common_current_user(); + + try { + $result = Profile_tag::setTags($user->id, $this->profile->id, $tags, $tag_priv); + if (!$result) { + throw new Exception('The tags could not be saved.'); + } + } catch (Exception $e) { + $this->showForm($e->getMessage()); + return false; + } + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _('Tags')); + $this->elementEnd('head'); + $this->elementStart('body'); + + if ($user->id == $this->profile->id) { + $widget = new SelftagsWidget($this, $user, $this->profile); + $widget->show(); + } else { + $widget = new PeopletagsWidget($this, $user, $this->profile); + $widget->show(); + } + + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->error = 'Tags saved.'; + $this->showForm(); + } + + Event::handle('EndSavePeopletags', array($this, $tagstring)); + } + } + + function showPageNotice() + { + if ($this->error) { + $this->element('p', 'error', $this->error); + } else { + $this->elementStart('div', 'instructions'); + $this->element('p', null, + _('Use this form to add tags to your subscribers or subscriptions.')); + $this->elementEnd('div'); + } + } +} + diff --git a/actions/unsubscribepeopletag.php b/actions/unsubscribepeopletag.php new file mode 100644 index 0000000000..a912cc10f9 --- /dev/null +++ b/actions/unsubscribepeopletag.php @@ -0,0 +1,142 @@ +. + * + * @category Peopletag + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Unsubscribe to a peopletag + * + * This is the action for subscribing to a peopletag. It works more or less like the join action + * for groups. + * + * @category Peopletag + * @package StatusNet + * @author Shashi Gowda + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class UnsubscribepeopletagAction extends Action +{ + var $peopletag = null; + var $tagger = null; + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to unsubscribe to a peopletag.')); + return false; + } + // Only allow POST requests + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError(_('This action only accepts POST requests.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + $tagger_arg = $this->trimmed('tagger'); + $tag_arg = $this->trimmed('tag'); + + $id = intval($this->arg('id')); + if ($id) { + $this->peopletag = Profile_list::staticGet('id', $id); + } else { + $this->clientError(_('No ID given.'), 404); + return false; + } + + if (!$this->peopletag || $this->peopletag->private) { + $this->clientError(_('No such peopletag.'), 404); + return false; + } + + $this->tagger = Profile::staticGet('id', $this->peopletag->tagger); + + return true; + } + + /** + * Handle the request + * + * On POST, add the current user to the group + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $cur = common_current_user(); + + Profile_tag_subscription::remove($this->peopletag, $cur); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, sprintf(_('%1$s unsubscribed to peopletag %2$s by %3$s'), + $cur->nickname, + $this->peopletag->tag, + $this->tagger->nickname)); + $this->elementEnd('head'); + $this->elementStart('body'); + $lf = new SubscribePeopletagForm($this, $this->peopletag); + $lf->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + if (common_get_returnto()) { + common_redirect(common_get_returnto(), 303); + return true; + } + common_redirect(common_local_url('peopletagsbyuser', + array('nickname' => $this->tagger->nickname)), + 303); + } + } +} diff --git a/lib/action.php b/lib/action.php index 31e52dfcf2..80b3176253 100644 --- a/lib/action.php +++ b/lib/action.php @@ -291,6 +291,8 @@ class Action extends HTMLOutputter // lawsuit $this->script('jquery.cookie.min.js'); $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.min.js').'"); }'); $this->script('jquery.joverlay.min.js'); + $this->inlineScript('function _loadTagInput(init) { $.getScript("'.common_path('js/jquery.timers.js'). '"); $.getScript("'.common_path('js/jquery.tagInput.js').'", init); } var _peopletagAC = "' . common_local_url('peopletagautocomplete') . '";'); + Event::handle('EndShowJQueryScripts', array($this)); } if (Event::handle('StartShowStatusNetScripts', array($this)) && diff --git a/lib/router.php b/lib/router.php index ee1e4cd849..7f17f2d0a5 100644 --- a/lib/router.php +++ b/lib/router.php @@ -227,7 +227,9 @@ class Router $m->connect('main/sup/:seconds', array('action' => 'sup'), array('seconds' => '[0-9]+')); - $m->connect('main/tagother/:id', array('action' => 'tagother')); + $m->connect('main/tagprofile', array('action' => 'tagprofile')); + $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'), + array('id' => '[0-9]+')); $m->connect('main/oembed', array('action' => 'oembed')); @@ -355,10 +357,6 @@ class Router array('action' => 'tag'), array('tag' => self::REGEX_TAG)); - $m->connect('peopletag/:tag', - array('action' => 'peopletag'), - array('tag' => self::REGEX_TAG)); - // groups $m->connect('group/new', array('action' => 'newgroup')); @@ -959,6 +957,76 @@ class Router array('nickname' => Nickname::DISPLAY_FMT)); } + // people tags + + $m->connect('peopletags', array('action' => 'publicpeopletagcloud')); + + $m->connect('peopletag/:tag', array('action' => 'peopletag', + 'tag' => self::REGEX_TAG)); + + $m->connect('selftag/:tag', array('action' => 'selftag', + 'tag' => self::REGEX_TAG)); + + $m->connect('main/addpeopletag', array('action' => 'addpeopletag')); + + $m->connect('main/removepeopletag', array('action' => 'removepeopletag')); + + $m->connect('main/profilecompletion', array('action' => 'profilecompletion')); + + $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete')); + + $m->connect(':nickname/peopletags', + array('action' => 'peopletagsbyuser', + 'nickname' => Nickname::DISPLAY_FMT)); + + $m->connect(':nickname/peopletags/private', + array('action' => 'peopletagsbyuser', + 'nickname' => Nickname::DISPLAY_FMT, + 'private' => 1)); + + $m->connect(':nickname/peopletags/public', + array('action' => 'peopletagsbyuser', + 'nickname' => Nickname::DISPLAY_FMT, + 'public' => 1)); + + $m->connect(':nickname/othertags', + array('action' => 'peopletagsforuser', + 'nickname' => Nickname::DISPLAY_FMT)); + + $m->connect(':nickname/peopletagsubscriptions', + array('action' => 'peopletagsubscriptions', + 'nickname' => Nickname::DISPLAY_FMT)); + + $m->connect(':tagger/all/:tag/subscribers', + array('action' => 'peopletagsubscribers', + 'tagger' => Nickname::DISPLAY_FMT, + 'tag' => self::REGEX_TAG)); + + $m->connect(':tagger/all/:tag/tagged', + array('action' => 'peopletagged', + 'tagger' => Nickname::DISPLAY_FMT, + 'tag' => self::REGEX_TAG)); + + $m->connect(':tagger/all/:tag/edit', + array('action' => 'editpeopletag', + 'tagger' => Nickname::DISPLAY_FMT, + 'tag' => self::REGEX_TAG)); + + foreach(array('subscribe', 'unsubscribe') as $v) { + $m->connect('peopletag/:id/'.$v, + array('action' => $v.'peopletag', + 'id' => '[0-9]{1,64}')); + } + $m->connect('user/:tagger_id/profiletag/:id/id', + array('action' => 'profiletagbyid', + 'tagger_id' => '[0-9]+', + 'id' => '[0-9]+')); + + $m->connect(':tagger/all/:tag', + array('action' => 'showprofiletag', + 'tagger' => Nickname::DISPLAY_FMT, + 'tag' => self::REGEX_TAG)); + foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', array('action' => $a),