forked from GNUsocial/gnu-social
Merge remote branch 'origin/1.0.x' into 1.0.x
This commit is contained in:
@@ -54,6 +54,7 @@ class ExtendedProfilePlugin extends Plugin
|
||||
function onAutoload($cls)
|
||||
{
|
||||
$lower = strtolower($cls);
|
||||
|
||||
switch ($lower)
|
||||
{
|
||||
case 'extendedprofile':
|
||||
@@ -62,6 +63,9 @@ class ExtendedProfilePlugin extends Plugin
|
||||
case 'profiledetailsettingsaction':
|
||||
require_once dirname(__FILE__) . '/' . $lower . '.php';
|
||||
return false;
|
||||
case 'userautocompleteaction':
|
||||
require_once dirname(__FILE__) . '/action/' . mb_substr($lower, 0, -6) . '.php';
|
||||
return false;
|
||||
case 'profile_detail':
|
||||
require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php';
|
||||
return false;
|
||||
@@ -81,11 +85,19 @@ class ExtendedProfilePlugin extends Plugin
|
||||
*/
|
||||
function onStartInitializeRouter($m)
|
||||
{
|
||||
$m->connect(':nickname/detail',
|
||||
array('action' => 'profiledetail'),
|
||||
array('nickname' => Nickname::DISPLAY_FMT));
|
||||
$m->connect('settings/profile/detail',
|
||||
array('action' => 'profiledetailsettings'));
|
||||
$m->connect(
|
||||
':nickname/detail',
|
||||
array('action' => 'profiledetail'),
|
||||
array('nickname' => Nickname::DISPLAY_FMT)
|
||||
);
|
||||
$m->connect(
|
||||
'/settings/profile/finduser',
|
||||
array('action' => 'Userautocomplete')
|
||||
);
|
||||
$m->connect(
|
||||
'settings/profile/detail',
|
||||
array('action' => 'profiledetailsettings')
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -95,8 +107,6 @@ class ExtendedProfilePlugin extends Plugin
|
||||
$schema = Schema::get();
|
||||
$schema->ensureTable('profile_detail', Profile_detail::schemaDef());
|
||||
|
||||
// @hack until key definition support is merged
|
||||
Profile_detail::fixIndexes($schema);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -21,130 +21,122 @@ if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class Profile_detail extends Memcached_DataObject
|
||||
/**
|
||||
* DataObject class to store extended profile fields. Allows for storing
|
||||
* multiple values per a "field_name" (field_name property is not unique).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Jed's Phone Numbers
|
||||
* home : 510-384-1992
|
||||
* mobile: 510-719-1139
|
||||
* work : 415-231-1121
|
||||
*
|
||||
* We can store these phone numbers in a "field" represented by three
|
||||
* Profile_detail objects, each named 'phone_number' like this:
|
||||
*
|
||||
* $phone1 = new Profile_detail();
|
||||
* $phone1->field_name = 'phone_number';
|
||||
* $phone1->rel = 'home';
|
||||
* $phone1->field_value = '510-384-1992';
|
||||
* $phone1->value_index = 1;
|
||||
*
|
||||
* $phone1 = new Profile_detail();
|
||||
* $phone1->field_name = 'phone_number';
|
||||
* $phone1->rel = 'mobile';
|
||||
* $phone1->field_value = '510-719-1139';
|
||||
* $phone1->value_index = 2;
|
||||
*
|
||||
* $phone1 = new Profile_detail();
|
||||
* $phone1->field_name = 'phone_number';
|
||||
* $phone1->rel = 'work';
|
||||
* $phone1->field_value = '415-231-1121';
|
||||
* $phone1->value_index = 3;
|
||||
*
|
||||
*/
|
||||
class Profile_detail extends Managed_DataObject
|
||||
{
|
||||
public $__table = 'submirror';
|
||||
public $__table = 'profile_detail';
|
||||
|
||||
public $id;
|
||||
|
||||
public $profile_id;
|
||||
public $field;
|
||||
public $field_index; // relative ordering of multiple values in the same field
|
||||
|
||||
public $value; // primary text value
|
||||
public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM
|
||||
public $profile_id; // profile this is for
|
||||
public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM
|
||||
public $field_name; // name
|
||||
public $field_value; // primary text value
|
||||
public $value_index; // relative ordering of multiple values in the same field
|
||||
public $date; // related date
|
||||
public $ref_profile; // for people types, allows pointing to a known profile in the system
|
||||
|
||||
public $created;
|
||||
public $modified;
|
||||
|
||||
public /*static*/ function staticGet($k, $v=null)
|
||||
/**
|
||||
* Get an instance by key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given key value.
|
||||
*
|
||||
* @param string $k Key to use to lookup
|
||||
* @param mixed $v Value to lookup
|
||||
*
|
||||
* @return User_greeting_count object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
|
||||
function staticGet($k, $v=null)
|
||||
{
|
||||
return parent::staticGet(__CLASS__, $k, $v);
|
||||
return Memcached_DataObject::staticGet('Profile_detail', $k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* return table definition for DB_DataObject
|
||||
* Get an instance by compound key
|
||||
*
|
||||
* DB_DataObject needs to know something about the table to manipulate
|
||||
* instances. This method provides all the DB_DataObject needs to know.
|
||||
* This is a utility method to get a single instance with a given set of
|
||||
* key-value pairs. Usually used for the primary key for a compound key; thus
|
||||
* the name.
|
||||
*
|
||||
* @param array $kv array of key-value mappings
|
||||
*
|
||||
* @return Bookmark object found, or null for no hits
|
||||
*
|
||||
* @return array array of column definitions
|
||||
*/
|
||||
|
||||
function table()
|
||||
function pkeyGet($kv)
|
||||
{
|
||||
return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
|
||||
'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
'field' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
|
||||
'field_index' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
|
||||
|
||||
'value' => DB_DATAOBJECT_STR,
|
||||
'rel' => DB_DATAOBJECT_STR,
|
||||
'ref_profile' => DB_DATAOBJECT_ID,
|
||||
|
||||
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
|
||||
'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
|
||||
return Memcached_DataObject::pkeyGet('Profile_detail', $kv);
|
||||
}
|
||||
|
||||
static function schemaDef()
|
||||
{
|
||||
// @fixme need a reverse key on (subscribed, subscriber) as well
|
||||
return array(new ColumnDef('id', 'integer',
|
||||
null, false, 'PRI'),
|
||||
|
||||
// @fixme need a unique index on these three
|
||||
new ColumnDef('profile_id', 'integer',
|
||||
null, false),
|
||||
new ColumnDef('field', 'varchar',
|
||||
16, false),
|
||||
new ColumnDef('field_index', 'integer',
|
||||
null, false),
|
||||
|
||||
new ColumnDef('value', 'text',
|
||||
null, true),
|
||||
new ColumnDef('rel', 'varchar',
|
||||
16, true),
|
||||
new ColumnDef('ref_profile', 'integer',
|
||||
null, true),
|
||||
|
||||
new ColumnDef('created', 'datetime',
|
||||
null, false),
|
||||
new ColumnDef('modified', 'datetime',
|
||||
null, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary hack to set up the compound index, since we can't do
|
||||
* it yet through regular Schema interface. (Coming for 1.0...)
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @return void
|
||||
*/
|
||||
static function fixIndexes($schema)
|
||||
{
|
||||
try {
|
||||
// @fixme this won't be a unique index... SIGH
|
||||
$schema->createIndex('profile_detail', array('profile_id', 'field', 'field_index'));
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, __METHOD__ . ': ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return key definitions for DB_DataObject
|
||||
*
|
||||
* DB_DataObject needs to know about keys that the table has; this function
|
||||
* defines them.
|
||||
*
|
||||
* @return array key definitions
|
||||
*/
|
||||
|
||||
function keys()
|
||||
{
|
||||
return array_keys($this->keyTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* return key definitions for Memcached_DataObject
|
||||
*
|
||||
* Our caching system uses the same key definitions, but uses a different
|
||||
* method to get them.
|
||||
*
|
||||
* @return array key definitions
|
||||
*/
|
||||
|
||||
function keyTypes()
|
||||
{
|
||||
// @fixme keys
|
||||
// need a sane key for reverse lookup too
|
||||
return array('id' => 'K');
|
||||
}
|
||||
|
||||
function sequenceKey()
|
||||
{
|
||||
return array('id', true);
|
||||
return array(
|
||||
'description'
|
||||
=> 'Additional profile details for the ExtendedProfile plugin',
|
||||
'fields' => array(
|
||||
'id' => array('type' => 'serial', 'not null' => true),
|
||||
'profile_id' => array('type' => 'int', 'not null' => true),
|
||||
'field_name' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 16,
|
||||
'not null' => true
|
||||
),
|
||||
'value_index' => array('type' => 'int'),
|
||||
'field_value' => array('type' => 'text'),
|
||||
'date' => array('type' => 'datetime'),
|
||||
'rel' => array('type' => 'varchar', 'length' => 16),
|
||||
'rel_profile' => array('type' => 'int'),
|
||||
'created' => array(
|
||||
'type' => 'datetime',
|
||||
'not null' => true
|
||||
),
|
||||
'modified' => array(
|
||||
'type' => 'timestamp',
|
||||
'not null' => true
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
'unique keys' => array(
|
||||
'profile_detail_profile_id_field_name_value_index'
|
||||
=> array('profile_id', 'field_name', 'value_index'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
113
plugins/ExtendedProfile/action/userautocomplete.php
Normal file
113
plugins/ExtendedProfile/action/userautocomplete.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Action for showing Twitter-like JSON search results
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Search
|
||||
* @package StatusNet
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2011 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')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
class UserautocompleteAction extends Action
|
||||
{
|
||||
var $query;
|
||||
|
||||
/**
|
||||
* Initialization.
|
||||
*
|
||||
* @param array $args Web and URL arguments
|
||||
*
|
||||
* @return boolean true if nothing goes wrong
|
||||
*/
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
$this->query = $this->trimmed('term');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request
|
||||
*
|
||||
* @param array $args Arguments from $_REQUEST
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
$this->showResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for users matching the query and spit the results out
|
||||
* as a quick-n-dirty JSON document
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function showResults()
|
||||
{
|
||||
$people = array();
|
||||
|
||||
$profile = new Profile();
|
||||
|
||||
$search_engine = $profile->getSearchEngine('profile');
|
||||
$search_engine->set_sort_mode('nickname_desc');
|
||||
$search_engine->limit(0, 10);
|
||||
$search_engine->query(strtolower($this->query . '*'));
|
||||
|
||||
$cnt = $profile->find();
|
||||
|
||||
if ($cnt > 0) {
|
||||
|
||||
$sql = 'SELECT profile.* FROM profile, user WHERE profile.id = user.id '
|
||||
. ' AND LEFT(LOWER(profile.nickname), '
|
||||
. strlen($this->query)
|
||||
. ') = \'%s\' '
|
||||
. ' LIMIT 0, 10';
|
||||
|
||||
$profile->query(sprintf($sql, $this->query));
|
||||
}
|
||||
|
||||
while ($profile->fetch()) {
|
||||
$people[] = $profile->nickname;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
print json_encode($people);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do we need to write to the database?
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
function isReadOnly($args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
164
plugins/ExtendedProfile/css/profiledetail.css
Normal file
164
plugins/ExtendedProfile/css/profiledetail.css
Normal file
@@ -0,0 +1,164 @@
|
||||
/* Note the #content is only needed to override weird crap in default styles */
|
||||
|
||||
#profiledetail .entity_actions {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#profiledetail #content h3 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#content table.extended-profile {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#content table.extended-profile th {
|
||||
color: #777;
|
||||
background-color: #ECECF2;
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
padding: 2px 8px 2px 0px;
|
||||
}
|
||||
|
||||
#content table.extended-profile th.employer, #content table.extended-profile th.institution {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#content table.extended-profile td {
|
||||
padding: 2px 0px 2px 8px;
|
||||
}
|
||||
|
||||
.experience-item, .education-item {
|
||||
float: left;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.experience-item .label, .education-item .label {
|
||||
float: left;
|
||||
clear: left;
|
||||
position: relative;
|
||||
left: -8px;
|
||||
margin-right: 2px;
|
||||
margin-bottom: 8px;
|
||||
color: #777;
|
||||
background-color: #ECECF2;
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
padding: 2px 8px 2px 0px;
|
||||
}
|
||||
|
||||
.experience-item .field, .education-item .field {
|
||||
float: left;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
#profiledetailsettings #content table.extended-profile td {
|
||||
padding: 0px 0px 0px 8px;
|
||||
}
|
||||
|
||||
#profiledetailsettings input {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.form_settings .extended-profile label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.extended-profile textarea {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.extended-profile input[type=text] {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.extended-profile .phone-item input[type=text], .extended-profile .im-item input[type=text], .extended-profile .website-item input[type=text] {
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.extended-profile input.hasDatepicker {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.experience-item input[type=text], .education-item input[type=text] {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.extended-profile .current-checkbox {
|
||||
float: left;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.form_settings .extended-profile input.checkbox {
|
||||
margin-left: 0px;
|
||||
left: 0px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.form_settings .extended-profile label.checkbox {
|
||||
max-width: 100%;
|
||||
float: none;
|
||||
display: inline;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.extended-profile select {
|
||||
padding-right: 2px;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
.extended-profile a.add_row, .extended-profile a.remove_row {
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
overflow: hidden;
|
||||
background-image: url('../../../theme/rebase/images/icons/icons-01.gif');
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.extended-profile a.remove_row {
|
||||
background-position: 0px -1252px;
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
line-height: 4em;
|
||||
}
|
||||
|
||||
.extended-profile a.add_row {
|
||||
clear: both;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
left: 2px;
|
||||
background-position: 0px -1186px;
|
||||
width: 120px;
|
||||
padding-left: 20px;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
#content table.extended-profile .supersizeme th {
|
||||
border-bottom: 28px solid #fff;
|
||||
}
|
||||
|
||||
#profiledetailsettings .experience-item, #profiledetailsettings .education-item {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#profiledetailsettings .education-item textarea {
|
||||
float: left;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#profiledetailsettings tr:last-child .experience-item, #profiledetailsettings tr:last-child .education-item {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#profiledetailsettings .experience-item a.add_row, #profiledetailsettings .education-item a.add_row {
|
||||
left: 160px;
|
||||
}
|
@@ -21,27 +21,256 @@ if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to represent extended profile data
|
||||
*/
|
||||
class ExtendedProfile
|
||||
{
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Profile $profile
|
||||
*/
|
||||
function __construct(Profile $profile)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->profile = $profile;
|
||||
$this->user = $profile->getUser();
|
||||
$this->fields = $this->loadFields();
|
||||
$this->sections = $this->getSections();
|
||||
$this->fields = $this->loadFields();
|
||||
//common_debug(var_export($this->sections, true));
|
||||
|
||||
//common_debug(var_export($this->fields, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load extended profile fields
|
||||
*
|
||||
* @return array $fields the list of fields
|
||||
*/
|
||||
function loadFields()
|
||||
{
|
||||
$detail = new Profile_detail();
|
||||
$detail->profile_id = $this->profile->id;
|
||||
$detail->find();
|
||||
|
||||
while ($detail->get()) {
|
||||
$fields[$detail->field][] = clone($detail);
|
||||
|
||||
$fields = array();
|
||||
|
||||
while ($detail->fetch()) {
|
||||
$fields[$detail->field_name][] = clone($detail);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a the self-tags associated with this profile
|
||||
*
|
||||
* @return string the concatenated string of tags
|
||||
*/
|
||||
function getTags()
|
||||
{
|
||||
return implode(' ', $this->user->getSelfTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a simple string value. Checks for fields that should
|
||||
* be stored in the regular profile and returns values from it
|
||||
* if appropriate.
|
||||
*
|
||||
* @param string $name name of the detail field to get the
|
||||
* value from
|
||||
*
|
||||
* @return string the value
|
||||
*/
|
||||
function getTextValue($name)
|
||||
{
|
||||
$key = strtolower($name);
|
||||
$profileFields = array('fullname', 'location', 'bio');
|
||||
|
||||
if (in_array($key, $profileFields)) {
|
||||
return $this->profile->$name;
|
||||
} else if (array_key_exists($key, $this->fields)) {
|
||||
return $this->fields[$key][0]->field_value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getDateValue($name) {
|
||||
$key = strtolower($name);
|
||||
if (array_key_exists($key, $this->fields)) {
|
||||
return $this->fields[$key][0]->date;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: getPhones, getIms, and getWebsites pretty much do the same thing,
|
||||
// so refactor.
|
||||
function getPhones()
|
||||
{
|
||||
$phones = (isset($this->fields['phone'])) ? $this->fields['phone'] : null;
|
||||
$pArrays = array();
|
||||
|
||||
if (empty($phones)) {
|
||||
$pArrays[] = array(
|
||||
'label' => _m('Phone'),
|
||||
'index' => 0,
|
||||
'type' => 'phone',
|
||||
'vcard' => 'tel',
|
||||
'rel' => 'office',
|
||||
'value' => null
|
||||
);
|
||||
} else {
|
||||
for ($i = 0; $i < sizeof($phones); $i++) {
|
||||
$pa = array(
|
||||
'label' => _m('Phone'),
|
||||
'type' => 'phone',
|
||||
'index' => intval($phones[$i]->value_index),
|
||||
'rel' => $phones[$i]->rel,
|
||||
'value' => $phones[$i]->field_value,
|
||||
'vcard' => 'tel'
|
||||
);
|
||||
|
||||
$pArrays[] = $pa;
|
||||
}
|
||||
}
|
||||
return $pArrays;
|
||||
}
|
||||
|
||||
function getIms()
|
||||
{
|
||||
$ims = (isset($this->fields['im'])) ? $this->fields['im'] : null;
|
||||
$iArrays = array();
|
||||
|
||||
if (empty($ims)) {
|
||||
$iArrays[] = array(
|
||||
'label' => _m('IM'),
|
||||
'type' => 'im'
|
||||
);
|
||||
} else {
|
||||
for ($i = 0; $i < sizeof($ims); $i++) {
|
||||
$ia = array(
|
||||
'label' => _m('IM'),
|
||||
'type' => 'im',
|
||||
'index' => intval($ims[$i]->value_index),
|
||||
'rel' => $ims[$i]->rel,
|
||||
'value' => $ims[$i]->field_value,
|
||||
);
|
||||
|
||||
$iArrays[] = $ia;
|
||||
}
|
||||
}
|
||||
return $iArrays;
|
||||
}
|
||||
|
||||
function getWebsites()
|
||||
{
|
||||
$sites = (isset($this->fields['website'])) ? $this->fields['website'] : null;
|
||||
$wArrays = array();
|
||||
|
||||
if (empty($sites)) {
|
||||
$wArrays[] = array(
|
||||
'label' => _m('Website'),
|
||||
'type' => 'website'
|
||||
);
|
||||
} else {
|
||||
for ($i = 0; $i < sizeof($sites); $i++) {
|
||||
$wa = array(
|
||||
'label' => _m('Website'),
|
||||
'type' => 'website',
|
||||
'index' => intval($sites[$i]->value_index),
|
||||
'rel' => $sites[$i]->rel,
|
||||
'value' => $sites[$i]->field_value,
|
||||
);
|
||||
|
||||
$wArrays[] = $wa;
|
||||
}
|
||||
}
|
||||
return $wArrays;
|
||||
}
|
||||
|
||||
function getExperiences()
|
||||
{
|
||||
$companies = (isset($this->fields['company'])) ? $this->fields['company'] : null;
|
||||
$start = (isset($this->fields['start'])) ? $this->fields['start'] : null;
|
||||
$end = (isset($this->fields['end'])) ? $this->fields['end'] : null;
|
||||
|
||||
$eArrays = array();
|
||||
|
||||
if (empty($companies)) {
|
||||
$eArrays[] = array(
|
||||
'label' => _m('Employer'),
|
||||
'type' => 'experience',
|
||||
'company' => null,
|
||||
'start' => null,
|
||||
'end' => null,
|
||||
'current' => false,
|
||||
'index' => 0
|
||||
);
|
||||
} else {
|
||||
for ($i = 0; $i < sizeof($companies); $i++) {
|
||||
$ea = array(
|
||||
'label' => _m('Employer'),
|
||||
'type' => 'experience',
|
||||
'company' => $companies[$i]->field_value,
|
||||
'index' => intval($companies[$i]->value_index),
|
||||
'current' => $end[$i]->rel,
|
||||
'start' => $start[$i]->date,
|
||||
'end' => $end[$i]->date
|
||||
);
|
||||
$eArrays[] = $ea;
|
||||
}
|
||||
}
|
||||
return $eArrays;
|
||||
}
|
||||
|
||||
function getEducation()
|
||||
{
|
||||
$schools = (isset($this->fields['school'])) ? $this->fields['school'] : null;
|
||||
$degrees = (isset($this->fields['degree'])) ? $this->fields['degree'] : null;
|
||||
$descs = (isset($this->fields['degree_descr'])) ? $this->fields['degree_descr'] : null;
|
||||
$start = (isset($this->fields['school_start'])) ? $this->fields['school_start'] : null;
|
||||
$end = (isset($this->fields['school_end'])) ? $this->fields['school_end'] : null;
|
||||
$iArrays = array();
|
||||
|
||||
if (empty($schools)) {
|
||||
$iArrays[] = array(
|
||||
'type' => 'education',
|
||||
'label' => _m('Institution'),
|
||||
'school' => null,
|
||||
'degree' => null,
|
||||
'description' => null,
|
||||
'start' => null,
|
||||
'end' => null,
|
||||
'index' => 0
|
||||
);
|
||||
} else {
|
||||
for ($i = 0; $i < sizeof($schools); $i++) {
|
||||
$ia = array(
|
||||
'type' => 'education',
|
||||
'label' => _m('Institution'),
|
||||
'school' => $schools[$i]->field_value,
|
||||
'degree' => isset($degrees[$i]->field_value) ? $degrees[$i]->field_value : null,
|
||||
'description' => isset($descs[$i]->field_value) ? $descs[$i]->field_value : null,
|
||||
'index' => intval($schools[$i]->value_index),
|
||||
'start' => $start[$i]->date,
|
||||
'end' => $end[$i]->date
|
||||
);
|
||||
$iArrays[] = $ia;
|
||||
}
|
||||
}
|
||||
|
||||
return $iArrays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the sections of the extended profile
|
||||
*
|
||||
* @return array the big list of sections and fields
|
||||
*/
|
||||
function getSections()
|
||||
{
|
||||
return array(
|
||||
@@ -81,22 +310,9 @@ class ExtendedProfile
|
||||
'contact' => array(
|
||||
'label' => _m('Contact'),
|
||||
'fields' => array(
|
||||
'phone' => array(
|
||||
'label' => _m('Phone'),
|
||||
'type' => 'phone',
|
||||
'multi' => true,
|
||||
'vcard' => 'tel',
|
||||
),
|
||||
'im' => array(
|
||||
'label' => _m('IM'),
|
||||
'type' => 'im',
|
||||
'multi' => true,
|
||||
),
|
||||
'website' => array(
|
||||
'label' => _m('Websites'),
|
||||
'type' => 'website',
|
||||
'multi' => true,
|
||||
),
|
||||
'phone' => $this->getPhones(),
|
||||
'im' => $this->getIms(),
|
||||
'website' => $this->getWebsites()
|
||||
),
|
||||
),
|
||||
'personal' => array(
|
||||
@@ -119,19 +335,13 @@ class ExtendedProfile
|
||||
'experience' => array(
|
||||
'label' => _m('Work experience'),
|
||||
'fields' => array(
|
||||
'experience' => array(
|
||||
'type' => 'experience',
|
||||
'label' => _m('Employer'),
|
||||
),
|
||||
'experience' => $this->getExperiences()
|
||||
),
|
||||
),
|
||||
'education' => array(
|
||||
'label' => _m('Education'),
|
||||
'fields' => array(
|
||||
'education' => array(
|
||||
'type' => 'education',
|
||||
'label' => _m('Institution'),
|
||||
),
|
||||
'education' => $this->getEducation()
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@@ -21,13 +21,35 @@ if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class ExtendedProfileWidget extends Widget
|
||||
/**
|
||||
* Class for outputting a widget to display or edit
|
||||
* extended profiles
|
||||
*/
|
||||
class ExtendedProfileWidget extends Form
|
||||
{
|
||||
const EDITABLE=true;
|
||||
const EDITABLE = true;
|
||||
|
||||
/**
|
||||
* The parent profile
|
||||
*
|
||||
* @var Profile
|
||||
*/
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* The extended profile
|
||||
*
|
||||
* @var Extended_profile
|
||||
*/
|
||||
protected $ext;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param XMLOutputter $out
|
||||
* @param Profile $profile
|
||||
* @param boolean $editable
|
||||
*/
|
||||
public function __construct(XMLOutputter $out=null, Profile $profile=null, $editable=false)
|
||||
{
|
||||
parent::__construct($out);
|
||||
@@ -38,7 +60,37 @@ class ExtendedProfileWidget extends Widget
|
||||
$this->editable = $editable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the extended profile, or the edit form
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
if ($this->editable) {
|
||||
parent::show();
|
||||
} else {
|
||||
$this->showSections();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show form data
|
||||
*/
|
||||
public function formData()
|
||||
{
|
||||
// For JQuery UI modal dialog
|
||||
$this->out->elementStart(
|
||||
'div',
|
||||
array('id' => 'confirm-dialog', 'title' => 'Confirmation Required')
|
||||
);
|
||||
$this->out->text('Really delete this entry?');
|
||||
$this->out->elementEnd('div');
|
||||
$this->showSections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show each section of the extended profile
|
||||
*/
|
||||
public function showSections()
|
||||
{
|
||||
$sections = $this->ext->getSections();
|
||||
foreach ($sections as $name => $section) {
|
||||
@@ -46,21 +98,45 @@ class ExtendedProfileWidget extends Widget
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an extended profile section
|
||||
*
|
||||
* @param string $name name of the section
|
||||
* @param array $section array of fields for the section
|
||||
*/
|
||||
protected function showExtendedProfileSection($name, $section)
|
||||
{
|
||||
$this->out->element('h3', null, $section['label']);
|
||||
$this->out->elementStart('table', array('class' => 'extended-profile'));
|
||||
|
||||
foreach ($section['fields'] as $fieldName => $field) {
|
||||
$this->showExtendedProfileField($fieldName, $field);
|
||||
|
||||
switch($fieldName) {
|
||||
case 'phone':
|
||||
case 'im':
|
||||
case 'website':
|
||||
case 'experience':
|
||||
case 'education':
|
||||
$this->showMultiple($fieldName, $field);
|
||||
break;
|
||||
default:
|
||||
$this->showExtendedProfileField($fieldName, $field);
|
||||
}
|
||||
}
|
||||
$this->out->elementEnd('table');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an extended profile field
|
||||
*
|
||||
* @param string $name name of the field
|
||||
* @param array $field set of key/value pairs for the field
|
||||
*/
|
||||
protected function showExtendedProfileField($name, $field)
|
||||
{
|
||||
$this->out->elementStart('tr');
|
||||
|
||||
$this->out->element('th', null, $field['label']);
|
||||
$this->out->element('th', str_replace(' ','_',strtolower($field['label'])), $field['label']);
|
||||
|
||||
$this->out->elementStart('td');
|
||||
if ($this->editable) {
|
||||
@@ -73,30 +149,504 @@ class ExtendedProfileWidget extends Widget
|
||||
$this->out->elementEnd('tr');
|
||||
}
|
||||
|
||||
protected function showFieldValue($name, $field)
|
||||
{
|
||||
$this->out->text($name);
|
||||
protected function showMultiple($name, $fields) {
|
||||
foreach ($fields as $field) {
|
||||
$this->showExtendedProfileField($name, $field);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: showPhone, showIm and showWebsite all work the same, so
|
||||
// combine
|
||||
protected function showPhone($name, $field)
|
||||
{
|
||||
$this->out->elementStart('div', array('class' => 'phone-display'));
|
||||
$this->out->text($field['value']);
|
||||
if (!empty($field['rel'])) {
|
||||
$this->out->text(' (' . $field['rel'] . ')');
|
||||
}
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showIm($name, $field)
|
||||
{
|
||||
$this->out->elementStart('div', array('class' => 'im-display'));
|
||||
$this->out->text($field['value']);
|
||||
if (!empty($field['rel'])) {
|
||||
$this->out->text(' (' . $field['rel'] . ')');
|
||||
}
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showWebsite($name, $field)
|
||||
{
|
||||
$this->out->elementStart('div', array('class' => 'website-display'));
|
||||
|
||||
$url = $field['value'];
|
||||
|
||||
$this->out->element(
|
||||
"a",
|
||||
array(
|
||||
'href' => $url,
|
||||
'class' => 'extended-profile-link',
|
||||
'target' => "_blank"
|
||||
),
|
||||
$url
|
||||
);
|
||||
|
||||
if (!empty($field['rel'])) {
|
||||
$this->out->text(' (' . $field['rel'] . ')');
|
||||
}
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showEditableIm($name, $field)
|
||||
{
|
||||
$index = isset($field['index']) ? $field['index'] : 0;
|
||||
$id = "extprofile-$name-$index";
|
||||
$rel = $id . '-rel';
|
||||
$this->out->elementStart(
|
||||
'div', array(
|
||||
'id' => $id . '-edit',
|
||||
'class' => 'im-item'
|
||||
)
|
||||
);
|
||||
$this->out->input(
|
||||
$id,
|
||||
null,
|
||||
isset($field['value']) ? $field['value'] : null
|
||||
);
|
||||
$this->out->dropdown(
|
||||
$id . '-rel',
|
||||
'Type',
|
||||
array(
|
||||
'jabber' => 'Jabber',
|
||||
'gtalk' => 'GTalk',
|
||||
'aim' => 'AIM',
|
||||
'yahoo' => 'Yahoo! Messenger',
|
||||
'msn' => 'MSN',
|
||||
'skype' => 'Skype',
|
||||
'other' => 'Other'
|
||||
),
|
||||
null,
|
||||
false,
|
||||
isset($field['rel']) ? $field['rel'] : null
|
||||
);
|
||||
|
||||
$this->showMultiControls();
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showEditablePhone($name, $field)
|
||||
{
|
||||
$index = isset($field['index']) ? $field['index'] : 0;
|
||||
$id = "extprofile-$name-$index";
|
||||
$rel = $id . '-rel';
|
||||
$this->out->elementStart(
|
||||
'div', array(
|
||||
'id' => $id . '-edit',
|
||||
'class' => 'phone-item'
|
||||
)
|
||||
);
|
||||
$this->out->input(
|
||||
$id,
|
||||
null,
|
||||
isset($field['value']) ? $field['value'] : null
|
||||
);
|
||||
$this->out->dropdown(
|
||||
$id . '-rel',
|
||||
'Type',
|
||||
array(
|
||||
'office' => 'Office',
|
||||
'mobile' => 'Mobile',
|
||||
'home' => 'Home',
|
||||
'pager' => 'Pager',
|
||||
'other' => 'Other'
|
||||
),
|
||||
null,
|
||||
false,
|
||||
isset($field['rel']) ? $field['rel'] : null
|
||||
);
|
||||
|
||||
$this->showMultiControls();
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showEditableWebsite($name, $field)
|
||||
{
|
||||
$index = isset($field['index']) ? $field['index'] : 0;
|
||||
$id = "extprofile-$name-$index";
|
||||
$rel = $id . '-rel';
|
||||
$this->out->elementStart(
|
||||
'div', array(
|
||||
'id' => $id . '-edit',
|
||||
'class' => 'website-item'
|
||||
)
|
||||
);
|
||||
$this->out->input(
|
||||
$id,
|
||||
null,
|
||||
isset($field['value']) ? $field['value'] : null
|
||||
);
|
||||
$this->out->dropdown(
|
||||
$id . '-rel',
|
||||
'Type',
|
||||
array(
|
||||
'blog' => 'Blog',
|
||||
'homepage' => 'Homepage',
|
||||
'facebook' => 'Facebook',
|
||||
'linkedin' => 'LinkedIn',
|
||||
'flickr' => 'Flickr',
|
||||
'google' => 'Google Profile',
|
||||
'other' => 'Other',
|
||||
'twitter' => 'Twitter'
|
||||
),
|
||||
null,
|
||||
false,
|
||||
isset($field['rel']) ? $field['rel'] : null
|
||||
);
|
||||
|
||||
$this->showMultiControls();
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showExperience($name, $field)
|
||||
{
|
||||
$this->out->elementStart('div', 'experience-item');
|
||||
$this->out->element('div', 'label', _m('Company'));
|
||||
|
||||
if (!empty($field['company'])) {
|
||||
$this->out->element('div', 'field', $field['company']);
|
||||
|
||||
$this->out->element('div', 'label', _m('Start'));
|
||||
$this->out->element(
|
||||
'div',
|
||||
array('class' => 'field date'),
|
||||
date('j M Y', strtotime($field['start'])
|
||||
)
|
||||
);
|
||||
$this->out->element('div', 'label', _m('End'));
|
||||
$this->out->element(
|
||||
'div',
|
||||
array('class' => 'field date'),
|
||||
date('j M Y', strtotime($field['end'])
|
||||
)
|
||||
);
|
||||
|
||||
if ($field['current']) {
|
||||
$this->out->element(
|
||||
'div',
|
||||
array('class' => 'field current'),
|
||||
'(' . _m('Current') . ')'
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showEditableExperience($name, $field)
|
||||
{
|
||||
$index = isset($field['index']) ? $field['index'] : 0;
|
||||
$id = "extprofile-$name-$index";
|
||||
$this->out->elementStart(
|
||||
'div', array(
|
||||
'id' => $id . '-edit',
|
||||
'class' => 'experience-item'
|
||||
)
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('Company'));
|
||||
$this->out->input(
|
||||
$id,
|
||||
null,
|
||||
isset($field['company']) ? $field['company'] : null
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('Start'));
|
||||
$this->out->input(
|
||||
$id . '-start',
|
||||
null,
|
||||
isset($field['start']) ? date('j M Y', strtotime($field['start'])) : null
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('End'));
|
||||
|
||||
$this->out->input(
|
||||
$id . '-end',
|
||||
null,
|
||||
isset($field['end']) ? date('j M Y', strtotime($field['end'])) : null
|
||||
);
|
||||
$this->out->hidden(
|
||||
$id . '-current',
|
||||
'false'
|
||||
);
|
||||
$this->out->elementStart('div', 'current-checkbox');
|
||||
$this->out->checkbox(
|
||||
$id . '-current',
|
||||
_m('Current'),
|
||||
$field['current']
|
||||
);
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
$this->showMultiControls();
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showEducation($name, $field)
|
||||
{
|
||||
$this->out->elementStart('div', 'education-item');
|
||||
$this->out->element('div', 'label', _m('Institution'));
|
||||
$this->out->element('div', 'field', $field['school']);
|
||||
$this->out->element('div', 'label', _m('Degree'));
|
||||
$this->out->element('div', 'field', $field['degree']);
|
||||
$this->out->element('div', 'label', _m('Description'));
|
||||
$this->out->element('div', 'field', $field['description']);
|
||||
$this->out->element('div', 'label', _m('Start'));
|
||||
$this->out->element(
|
||||
'div',
|
||||
array('class' => 'field date'),
|
||||
date('j M Y', strtotime($field['start'])
|
||||
)
|
||||
);
|
||||
$this->out->element('div', 'label', _m('End'));
|
||||
$this->out->element(
|
||||
'div',
|
||||
array('class' => 'field date'),
|
||||
date('j M Y', strtotime($field['end'])
|
||||
)
|
||||
);
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
protected function showEditableEducation($name, $field)
|
||||
{
|
||||
$index = isset($field['index']) ? $field['index'] : 0;
|
||||
$id = "extprofile-$name-$index";
|
||||
$this->out->elementStart(
|
||||
'div', array(
|
||||
'id' => $id . '-edit',
|
||||
'class' => 'education-item'
|
||||
)
|
||||
);
|
||||
$this->out->element('div', 'label', _m('Institution'));
|
||||
$this->out->input(
|
||||
$id,
|
||||
null,
|
||||
isset($field['school']) ? $field['school'] : null
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('Degree'));
|
||||
$this->out->input(
|
||||
$id . '-degree',
|
||||
null,
|
||||
isset($field['degree']) ? $field['degree'] : null
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('Description'));
|
||||
$this->out->element('div', 'field', $field['description']);
|
||||
|
||||
$this->out->textarea(
|
||||
$id . '-description',
|
||||
null,
|
||||
isset($field['description']) ? $field['description'] : null
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('Start'));
|
||||
$this->out->input(
|
||||
$id . '-start',
|
||||
null,
|
||||
isset($field['start']) ? date('j M Y', strtotime($field['start'])) : null
|
||||
);
|
||||
|
||||
$this->out->element('div', 'label', _m('End'));
|
||||
$this->out->input(
|
||||
$id . '-end',
|
||||
null,
|
||||
isset($field['end']) ? date('j M Y', strtotime($field['end'])) : null
|
||||
);
|
||||
|
||||
$this->showMultiControls();
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
function showMultiControls()
|
||||
{
|
||||
$this->out->element(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'remove_row',
|
||||
'href' => 'javascript://',
|
||||
'style' => 'display: none;'
|
||||
),
|
||||
'-'
|
||||
);
|
||||
|
||||
$this->out->element(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'add_row',
|
||||
'href' => 'javascript://',
|
||||
'style' => 'display: none;'
|
||||
),
|
||||
'Add another item'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the value of a field
|
||||
*
|
||||
* @param string $name name of the field
|
||||
* @param array $field set of key/value pairs for the field
|
||||
*/
|
||||
protected function showFieldValue($name, $field)
|
||||
{
|
||||
$type = strval(@$field['type']);
|
||||
|
||||
switch($type)
|
||||
{
|
||||
case '':
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
$this->out->text($this->ext->getTextValue($name));
|
||||
break;
|
||||
case 'date':
|
||||
$value = $this->ext->getDateValue($name);
|
||||
if (!empty($value)) {
|
||||
$this->out->element(
|
||||
'div',
|
||||
array('class' => 'field date'),
|
||||
date('j M Y', strtotime($value))
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'person':
|
||||
$this->out->text($this->ext->getTextValue($name));
|
||||
break;
|
||||
case 'tags':
|
||||
$this->out->text($this->ext->getTags());
|
||||
break;
|
||||
case 'phone':
|
||||
$this->showPhone($name, $field);
|
||||
break;
|
||||
case 'website':
|
||||
$this->showWebsite($name, $field);
|
||||
break;
|
||||
case 'im':
|
||||
$this->showIm($name, $field);
|
||||
break;
|
||||
case 'experience':
|
||||
$this->showExperience($name, $field);
|
||||
break;
|
||||
case 'education':
|
||||
$this->showEducation($name, $field);
|
||||
break;
|
||||
default:
|
||||
$this->out->text("TYPE: $type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an editable version of the field
|
||||
*
|
||||
* @param string $name name fo the field
|
||||
* @param array $field array of key/value pairs for the field
|
||||
*/
|
||||
protected function showEditableField($name, $field)
|
||||
{
|
||||
$out = $this->out;
|
||||
//$out = new HTMLOutputter();
|
||||
// @fixme
|
||||
|
||||
$type = strval(@$field['type']);
|
||||
$id = "extprofile-" . $name;
|
||||
|
||||
$value = 'placeholder';
|
||||
|
||||
switch ($type) {
|
||||
case '':
|
||||
case 'text':
|
||||
$out->input($id, null, $value);
|
||||
break;
|
||||
case 'textarea':
|
||||
$out->textarea($id, null, $value);
|
||||
break;
|
||||
default:
|
||||
$out->input($id, null, "TYPE: $type");
|
||||
case '':
|
||||
case 'text':
|
||||
$out->input($id, null, $this->ext->getTextValue($name));
|
||||
break;
|
||||
case 'date':
|
||||
$out->input(
|
||||
$id,
|
||||
null,
|
||||
date('j M Y', strtotime($this->ext->getDateValue($name)))
|
||||
);
|
||||
break;
|
||||
case 'person':
|
||||
$out->input($id, null, $this->ext->getTextValue($name));
|
||||
break;
|
||||
case 'textarea':
|
||||
$out->textarea($id, null, $this->ext->getTextValue($name));
|
||||
break;
|
||||
case 'tags':
|
||||
$out->input($id, null, $this->ext->getTags());
|
||||
break;
|
||||
case 'phone':
|
||||
$this->showEditablePhone($name, $field);
|
||||
break;
|
||||
case 'im':
|
||||
$this->showEditableIm($name, $field);
|
||||
break;
|
||||
case 'website':
|
||||
$this->showEditableWebsite($name, $field);
|
||||
break;
|
||||
case 'experience':
|
||||
$this->showEditableExperience($name, $field);
|
||||
break;
|
||||
case 'education':
|
||||
$this->showEditableEducation($name, $field);
|
||||
break;
|
||||
default:
|
||||
$out->input($id, null, "TYPE: $type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit(
|
||||
'save',
|
||||
_m('BUTTON','Save'),
|
||||
'submit form_action-secondary',
|
||||
'save',
|
||||
_('Save details')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* @return string ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'profile-details-' . $this->profile->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* class of the form
|
||||
*
|
||||
* @return string of the form class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
return 'form_profile_details form_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of the form
|
||||
*
|
||||
* @return string URL of the action
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('profiledetailsettings');
|
||||
}
|
||||
}
|
||||
|
144
plugins/ExtendedProfile/js/profiledetail.js
Normal file
144
plugins/ExtendedProfile/js/profiledetail.js
Normal file
@@ -0,0 +1,144 @@
|
||||
var SN_EXTENDED = SN_EXTENDED || {};
|
||||
|
||||
SN_EXTENDED.reorder = function(cls) {
|
||||
|
||||
var divs = $('div[class=' + cls + ']');
|
||||
|
||||
$(divs).each(function(i, div) {
|
||||
$(div).find('a.add_row').hide();
|
||||
$(div).find('a.remove_row').show();
|
||||
SN_EXTENDED.replaceIndex(SN_EXTENDED.rowIndex(div), i);
|
||||
});
|
||||
|
||||
var lastDiv = $(divs).last().closest('tr');
|
||||
lastDiv.addClass('supersizeme');
|
||||
|
||||
$(divs).last().find('a.add_row').show();
|
||||
|
||||
if (divs.length == 1) {
|
||||
$(divs).find('a.remove_row').fadeOut("slow");
|
||||
}
|
||||
};
|
||||
|
||||
SN_EXTENDED.rowIndex = function(div) {
|
||||
var idstr = $(div).attr('id');
|
||||
var id = idstr.match(/\d+/);
|
||||
return id;
|
||||
};
|
||||
|
||||
SN_EXTENDED.rowCount = function(cls) {
|
||||
var divs = $.find('div[class=' + cls + ']');
|
||||
return divs.length;
|
||||
};
|
||||
|
||||
SN_EXTENDED.replaceIndex = function(elem, oldIndex, newIndex) {
|
||||
$(elem).find('*').each(function() {
|
||||
$.each(this.attributes, function(i, attrib) {
|
||||
var regexp = /extprofile-.*-\d.*/;
|
||||
var value = attrib.value;
|
||||
var match = value.match(regexp);
|
||||
if (match !== null) {
|
||||
attrib.value = value.replace("-" + oldIndex, "-" + newIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SN_EXTENDED.resetRow = function(elem) {
|
||||
$(elem).find('input, textarea').attr('value', '');
|
||||
$(elem).find('input').removeAttr('disabled');
|
||||
$(elem).find("select option[value='office']").attr("selected", true);
|
||||
$(elem).find("input:checkbox").attr('checked', false);
|
||||
$(elem).find("input[name$=-start], input[name$=-end]").each(function() {
|
||||
$(this).removeClass('hasDatepicker');
|
||||
$(this).datepicker({ dateFormat: 'd M yy' });
|
||||
});
|
||||
};
|
||||
|
||||
SN_EXTENDED.addRow = function() {
|
||||
var div = $(this).closest('div');
|
||||
var id = div.attr('id');
|
||||
var cls = div.attr('class');
|
||||
var index = id.match(/\d+/);
|
||||
var newIndex = parseInt(index) + 1;
|
||||
var newtr = $(div).closest('tr').removeClass('supersizeme').clone();
|
||||
SN_EXTENDED.replaceIndex(newtr, index, newIndex);
|
||||
SN_EXTENDED.resetRow(newtr);
|
||||
$(div).closest('tr').after(newtr);
|
||||
SN_EXTENDED.reorder(cls);
|
||||
};
|
||||
|
||||
SN_EXTENDED.removeRow = function() {
|
||||
|
||||
var div = $(this).closest('div');
|
||||
var id = $(div).attr('id');
|
||||
var cls = $(div).attr('class');
|
||||
var that = this;
|
||||
|
||||
$("#confirm-dialog").dialog({
|
||||
buttons : {
|
||||
"Confirm" : function() {
|
||||
$(this).dialog("close");
|
||||
var target = $(that).closest('tr');
|
||||
target.fadeOut("slow", function() {
|
||||
$(target).remove();
|
||||
SN_EXTENDED.reorder(cls);
|
||||
});
|
||||
},
|
||||
"Cancel" : function() {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var cnt = SN_EXTENDED.rowCount(cls);
|
||||
|
||||
if (cnt > 1) {
|
||||
$("#confirm-dialog").dialog("open");
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$("#confirm-dialog").dialog({
|
||||
autoOpen: false,
|
||||
modal: true
|
||||
});
|
||||
|
||||
$("input#extprofile-manager").autocomplete({
|
||||
source: 'finduser',
|
||||
minLength: 2 });
|
||||
|
||||
$("input[name$=-start], input[name$=-end], #extprofile-birthday").datepicker({ dateFormat: 'd M yy' });
|
||||
|
||||
var multifields = ["phone-item", "experience-item", "education-item", "im-item", 'website-item'];
|
||||
|
||||
for (f in multifields) {
|
||||
SN_EXTENDED.reorder(multifields[f]);
|
||||
}
|
||||
|
||||
$("input#extprofile-manager").autocomplete({
|
||||
source: 'finduser',
|
||||
minLength: 2 });
|
||||
|
||||
$('.add_row').live('click', SN_EXTENDED.addRow);
|
||||
$('.remove_row').live('click', SN_EXTENDED.removeRow);
|
||||
|
||||
$('input:checkbox[name$=current]').each(function() {
|
||||
var input = $(this).parent().siblings('input[id$=-end]');
|
||||
if ($(this).is(':checked')) {
|
||||
$(input).attr('disabled', 'true');
|
||||
}
|
||||
});
|
||||
|
||||
$('input:checkbox[name$=current]').live('click', function() {
|
||||
var input = $(this).parent().siblings('input[id$=-end]');
|
||||
if ($(this).is(':checked')) {
|
||||
$(input).val('');
|
||||
$(input).attr('disabled', 'true');
|
||||
} else {
|
||||
$(input).removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@@ -1,22 +0,0 @@
|
||||
/* Note the #content is only needed to override weird crap in default styles */
|
||||
|
||||
#content table.extended-profile {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 8px;
|
||||
}
|
||||
#content table.extended-profile th {
|
||||
color: #777;
|
||||
background-color: #eee;
|
||||
width: 150px;
|
||||
|
||||
padding-top: 0; /* override bizarre theme defaults */
|
||||
|
||||
text-align: right;
|
||||
padding-right: 8px;
|
||||
}
|
||||
#content table.extended-profile td {
|
||||
padding: 0; /* override bizarre theme defaults */
|
||||
|
||||
padding-left: 8px;
|
||||
}
|
@@ -21,8 +21,9 @@ if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class ProfileDetailAction extends ProfileAction
|
||||
class ProfileDetailAction extends ShowstreamAction
|
||||
{
|
||||
|
||||
function isReadOnly($args)
|
||||
{
|
||||
return true;
|
||||
@@ -33,28 +34,18 @@ class ProfileDetailAction extends ProfileAction
|
||||
return $this->profile->getFancyName();
|
||||
}
|
||||
|
||||
function showLocalNav()
|
||||
{
|
||||
$nav = new PersonalGroupNav($this);
|
||||
$nav->show();
|
||||
}
|
||||
|
||||
function showStylesheets() {
|
||||
parent::showStylesheets();
|
||||
$this->cssLink('plugins/ExtendedProfile/profiledetail.css');
|
||||
$this->cssLink('plugins/ExtendedProfile/css/profiledetail.css');
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
$this->showPage();
|
||||
}
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$cur = common_current_user();
|
||||
if ($cur && $cur->id == $this->profile->id) { // your own page
|
||||
$this->elementStart('div', 'entity_actions');
|
||||
$this->elementStart('ul');
|
||||
$this->elementStart('li', 'entity_edit');
|
||||
$this->element('a', array('href' => common_local_url('profiledetailsettings'),
|
||||
// TRANS: Link title for link on user profile.
|
||||
@@ -62,6 +53,7 @@ class ProfileDetailAction extends ProfileAction
|
||||
// TRANS: Link text for link on user profile.
|
||||
_m('Edit'));
|
||||
$this->elementEnd('li');
|
||||
$this->elementEnd('ul');
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class ProfileDetailSettingsAction extends AccountSettingsAction
|
||||
class ProfileDetailSettingsAction extends ProfileSettingsAction
|
||||
{
|
||||
|
||||
function title()
|
||||
@@ -43,13 +43,38 @@ class ProfileDetailSettingsAction extends AccountSettingsAction
|
||||
|
||||
function showStylesheets() {
|
||||
parent::showStylesheets();
|
||||
$this->cssLink('plugins/ExtendedProfile/profiledetail.css');
|
||||
$this->cssLink('plugins/ExtendedProfile/css/profiledetail.css');
|
||||
$this->cssLink('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css');
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle($args)
|
||||
function showScripts() {
|
||||
parent::showScripts();
|
||||
$this->script('plugins/ExtendedProfile/js/profiledetail.js');
|
||||
$this->script('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js');
|
||||
return true;
|
||||
}
|
||||
|
||||
function handlePost()
|
||||
{
|
||||
$this->showPage();
|
||||
// CSRF protection
|
||||
$token = $this->trimmed('token');
|
||||
if (!$token || $token != common_session_token()) {
|
||||
$this->showForm(
|
||||
_m(
|
||||
'There was a problem with your session token. '
|
||||
. 'Try again, please.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->arg('save')) {
|
||||
$this->saveDetails();
|
||||
} else {
|
||||
// TRANS: Message given submitting a form with an unknown action
|
||||
$this->showForm(_m('Unexpected form submission.'));
|
||||
}
|
||||
}
|
||||
|
||||
function showContent()
|
||||
@@ -57,7 +82,554 @@ class ProfileDetailSettingsAction extends AccountSettingsAction
|
||||
$cur = common_current_user();
|
||||
$profile = $cur->getProfile();
|
||||
|
||||
$widget = new ExtendedProfileWidget($this, $profile, ExtendedProfileWidget::EDITABLE);
|
||||
$widget = new ExtendedProfileWidget(
|
||||
$this,
|
||||
$profile,
|
||||
ExtendedProfileWidget::EDITABLE
|
||||
);
|
||||
$widget->show();
|
||||
}
|
||||
|
||||
function saveDetails()
|
||||
{
|
||||
common_debug(var_export($_POST, true));
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
try {
|
||||
$this->saveStandardProfileDetails($user);
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
$simpleFieldNames = array('title', 'spouse', 'kids', 'manager');
|
||||
$dateFieldNames = array('birthday');
|
||||
|
||||
foreach ($simpleFieldNames as $name) {
|
||||
$value = $this->trimmed('extprofile-' . $name);
|
||||
if (!empty($value)) {
|
||||
$this->saveField($user, $name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($dateFieldNames as $name) {
|
||||
$value = $this->trimmed('extprofile-' . $name);
|
||||
$dateVal = $this->parseDate($name, $value);
|
||||
$this->saveField(
|
||||
$user,
|
||||
$name,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$dateVal
|
||||
);
|
||||
}
|
||||
|
||||
$this->savePhoneNumbers($user);
|
||||
$this->saveIms($user);
|
||||
$this->saveWebsites($user);
|
||||
$this->saveExperiences($user);
|
||||
$this->saveEducations($user);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->showForm($e->getMessage(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->showForm(_('Details saved.'), true);
|
||||
|
||||
}
|
||||
|
||||
function parseDate($fieldname, $datestr, $required = false)
|
||||
{
|
||||
if (empty($datestr) && $required) {
|
||||
$msg = sprintf(
|
||||
_m('You must supply a date for "%s".'),
|
||||
$fieldname
|
||||
);
|
||||
throw new Exception($msg);
|
||||
} else {
|
||||
$ts = strtotime($datestr);
|
||||
if ($ts === false) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
_m('Invalid date entered for "%s": %s'),
|
||||
$fieldname,
|
||||
$ts
|
||||
)
|
||||
);
|
||||
}
|
||||
return common_sql_date($ts);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function savePhoneNumbers($user) {
|
||||
$phones = $this->findPhoneNumbers();
|
||||
$this->removeAll($user, 'phone');
|
||||
$i = 0;
|
||||
foreach($phones as $phone) {
|
||||
if (!empty($phone['value'])) {
|
||||
++$i;
|
||||
$this->saveField(
|
||||
$user,
|
||||
'phone',
|
||||
$phone['value'],
|
||||
$phone['rel'],
|
||||
$i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findPhoneNumbers() {
|
||||
|
||||
// Form vals look like this:
|
||||
// 'extprofile-phone-1' => '11332',
|
||||
// 'extprofile-phone-1-rel' => 'mobile',
|
||||
|
||||
$phones = $this->sliceParams('phone', 2);
|
||||
$phoneArray = array();
|
||||
|
||||
foreach ($phones as $phone) {
|
||||
list($number, $rel) = array_values($phone);
|
||||
$phoneArray[] = array(
|
||||
'value' => $number,
|
||||
'rel' => $rel
|
||||
);
|
||||
}
|
||||
|
||||
return $phoneArray;
|
||||
}
|
||||
|
||||
function findIms() {
|
||||
|
||||
// Form vals look like this:
|
||||
// 'extprofile-im-0' => 'jed',
|
||||
// 'extprofile-im-0-rel' => 'yahoo',
|
||||
|
||||
$ims = $this->sliceParams('im', 2);
|
||||
$imArray = array();
|
||||
|
||||
foreach ($ims as $im) {
|
||||
list($id, $rel) = array_values($im);
|
||||
$imArray[] = array(
|
||||
'value' => $id,
|
||||
'rel' => $rel
|
||||
);
|
||||
}
|
||||
|
||||
return $imArray;
|
||||
}
|
||||
|
||||
function saveIms($user) {
|
||||
$ims = $this->findIms();
|
||||
$this->removeAll($user, 'im');
|
||||
$i = 0;
|
||||
foreach($ims as $im) {
|
||||
if (!empty($im['value'])) {
|
||||
++$i;
|
||||
$this->saveField(
|
||||
$user,
|
||||
'im',
|
||||
$im['value'],
|
||||
$im['rel'],
|
||||
$i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findWebsites() {
|
||||
|
||||
// Form vals look like this:
|
||||
|
||||
$sites = $this->sliceParams('website', 2);
|
||||
$wsArray = array();
|
||||
|
||||
foreach ($sites as $site) {
|
||||
list($id, $rel) = array_values($site);
|
||||
$wsArray[] = array(
|
||||
'value' => $id,
|
||||
'rel' => $rel
|
||||
);
|
||||
}
|
||||
|
||||
return $wsArray;
|
||||
}
|
||||
|
||||
function saveWebsites($user) {
|
||||
$sites = $this->findWebsites();
|
||||
$this->removeAll($user, 'website');
|
||||
$i = 0;
|
||||
foreach($sites as $site) {
|
||||
|
||||
if (!Validate::uri(
|
||||
$site['value'],
|
||||
array('allowed_schemes' => array('http', 'https')))
|
||||
) {
|
||||
throw new Exception(sprintf(_m('Invalid URL: %s'), $site['value']));
|
||||
}
|
||||
|
||||
if (!empty($site['value'])) {
|
||||
++$i;
|
||||
$this->saveField(
|
||||
$user,
|
||||
'website',
|
||||
$site['value'],
|
||||
$site['rel'],
|
||||
$i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findExperiences() {
|
||||
|
||||
// Form vals look like this:
|
||||
// 'extprofile-experience-0' => 'Bozotronix',
|
||||
// 'extprofile-experience-0-current' => 'true'
|
||||
// 'extprofile-experience-0-start' => '1/5/10',
|
||||
// 'extprofile-experience-0-end' => '2/3/11',
|
||||
|
||||
$experiences = $this->sliceParams('experience', 4);
|
||||
$expArray = array();
|
||||
|
||||
foreach ($experiences as $exp) {
|
||||
if (sizeof($experiences) == 4) {
|
||||
list($company, $current, $end, $start) = array_values($exp);
|
||||
} else {
|
||||
$end = null;
|
||||
list($company, $current, $start) = array_values($exp);
|
||||
}
|
||||
if (!empty($company)) {
|
||||
$expArray[] = array(
|
||||
'company' => $company,
|
||||
'start' => $this->parseDate('Start', $start, true),
|
||||
'end' => ($current == 'false') ? $this->parseDate('End', $end, true) : null,
|
||||
'current' => ($current == 'false') ? false : true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $expArray;
|
||||
}
|
||||
|
||||
function saveExperiences($user) {
|
||||
common_debug('save experiences');
|
||||
$experiences = $this->findExperiences();
|
||||
|
||||
$this->removeAll($user, 'company');
|
||||
$this->removeAll($user, 'start');
|
||||
$this->removeAll($user, 'end'); // also stores 'current'
|
||||
|
||||
$i = 0;
|
||||
foreach($experiences as $experience) {
|
||||
if (!empty($experience['company'])) {
|
||||
++$i;
|
||||
$this->saveField(
|
||||
$user,
|
||||
'company',
|
||||
$experience['company'],
|
||||
null,
|
||||
$i
|
||||
);
|
||||
|
||||
$this->saveField(
|
||||
$user,
|
||||
'start',
|
||||
null,
|
||||
null,
|
||||
$i,
|
||||
$experience['start']
|
||||
);
|
||||
|
||||
// Save "current" employer indicator in rel
|
||||
if ($experience['current']) {
|
||||
$this->saveField(
|
||||
$user,
|
||||
'end',
|
||||
null,
|
||||
'current', // rel
|
||||
$i
|
||||
);
|
||||
} else {
|
||||
$this->saveField(
|
||||
$user,
|
||||
'end',
|
||||
null,
|
||||
null,
|
||||
$i,
|
||||
$experience['end']
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findEducations() {
|
||||
|
||||
// Form vals look like this:
|
||||
// 'extprofile-education-0-school' => 'Pigdog',
|
||||
// 'extprofile-education-0-degree' => 'BA',
|
||||
// 'extprofile-education-0-description' => 'Blar',
|
||||
// 'extprofile-education-0-start' => '05/22/99',
|
||||
// 'extprofile-education-0-end' => '05/22/05',
|
||||
|
||||
$edus = $this->sliceParams('education', 5);
|
||||
$eduArray = array();
|
||||
|
||||
foreach ($edus as $edu) {
|
||||
list($school, $degree, $description, $end, $start) = array_values($edu);
|
||||
if (!empty($school)) {
|
||||
$eduArray[] = array(
|
||||
'school' => $school,
|
||||
'degree' => $degree,
|
||||
'description' => $description,
|
||||
'start' => $this->parseDate('Start', $start, true),
|
||||
'end' => $this->parseDate('End', $end, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $eduArray;
|
||||
}
|
||||
|
||||
|
||||
function saveEducations($user) {
|
||||
common_debug('save education');
|
||||
$edus = $this->findEducations();
|
||||
common_debug(var_export($edus, true));
|
||||
|
||||
$this->removeAll($user, 'school');
|
||||
$this->removeAll($user, 'degree');
|
||||
$this->removeAll($user, 'degree_descr');
|
||||
$this->removeAll($user, 'school_start');
|
||||
$this->removeAll($user, 'school_end');
|
||||
|
||||
$i = 0;
|
||||
foreach($edus as $edu) {
|
||||
if (!empty($edu['school'])) {
|
||||
++$i;
|
||||
$this->saveField(
|
||||
$user,
|
||||
'school',
|
||||
$edu['school'],
|
||||
null,
|
||||
$i
|
||||
);
|
||||
$this->saveField(
|
||||
$user,
|
||||
'degree',
|
||||
$edu['degree'],
|
||||
null,
|
||||
$i
|
||||
);
|
||||
$this->saveField(
|
||||
$user,
|
||||
'degree_descr',
|
||||
$edu['description'],
|
||||
null,
|
||||
$i
|
||||
);
|
||||
$this->saveField(
|
||||
$user,
|
||||
'school_start',
|
||||
null,
|
||||
null,
|
||||
$i,
|
||||
$edu['start']
|
||||
);
|
||||
|
||||
$this->saveField(
|
||||
$user,
|
||||
'school_end',
|
||||
null,
|
||||
null,
|
||||
$i,
|
||||
$edu['end']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function arraySplit($array, $pieces)
|
||||
{
|
||||
if ($pieces < 2) {
|
||||
return array($array);
|
||||
}
|
||||
|
||||
$newCount = ceil(count($array) / $pieces);
|
||||
$a = array_slice($array, 0, $newCount);
|
||||
$b = $this->arraySplit(array_slice($array, $newCount), $pieces - 1);
|
||||
|
||||
return array_merge(array($a), $b);
|
||||
}
|
||||
|
||||
function findMultiParams($type) {
|
||||
$formVals = array();
|
||||
$target = $type;
|
||||
foreach ($_POST as $key => $val) {
|
||||
if (strrpos('extprofile-' . $key, $target) !== false) {
|
||||
$formVals[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $formVals;
|
||||
}
|
||||
|
||||
function sliceParams($key, $size) {
|
||||
$slice = array();
|
||||
$params = $this->findMultiParams($key);
|
||||
ksort($params);
|
||||
$slice = $this->arraySplit($params, sizeof($params) / $size);
|
||||
return $slice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an extended profile field as a Profile_detail
|
||||
*
|
||||
* @param User $user the current user
|
||||
* @param string $name field name
|
||||
* @param string $value field value
|
||||
* @param string $rel field rel (type)
|
||||
* @param int $index index (fields can have multiple values)
|
||||
* @param date $date related date
|
||||
*/
|
||||
function saveField($user, $name, $value, $rel = null, $index = null, $date = null)
|
||||
{
|
||||
$profile = $user->getProfile();
|
||||
$detail = new Profile_detail();
|
||||
|
||||
$detail->profile_id = $profile->id;
|
||||
$detail->field_name = $name;
|
||||
$detail->value_index = $index;
|
||||
|
||||
$result = $detail->find(true);
|
||||
|
||||
if (empty($result)) {
|
||||
$detial->value_index = $index;
|
||||
$detail->rel = $rel;
|
||||
$detail->field_value = $value;
|
||||
$detail->date = $date;
|
||||
$detail->created = common_sql_now();
|
||||
$result = $detail->insert();
|
||||
if (empty($result)) {
|
||||
common_log_db_error($detail, 'INSERT', __FILE__);
|
||||
$this->serverError(_m('Could not save profile details.'));
|
||||
}
|
||||
} else {
|
||||
$orig = clone($detail);
|
||||
|
||||
$detail->field_value = $value;
|
||||
$detail->rel = $rel;
|
||||
$detail->date = $date;
|
||||
|
||||
$result = $detail->update($orig);
|
||||
if (empty($result)) {
|
||||
common_log_db_error($detail, 'UPDATE', __FILE__);
|
||||
$this->serverError(_m('Could not save profile details.'));
|
||||
}
|
||||
}
|
||||
|
||||
$detail->free();
|
||||
}
|
||||
|
||||
function removeAll($user, $name)
|
||||
{
|
||||
$profile = $user->getProfile();
|
||||
$detail = new Profile_detail();
|
||||
$detail->profile_id = $profile->id;
|
||||
$detail->field_name = $name;
|
||||
$detail->delete();
|
||||
$detail->free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save fields that should be stored in the main profile object
|
||||
*
|
||||
* XXX: There's a lot of dupe code here from ProfileSettingsAction.
|
||||
* Do not want.
|
||||
*
|
||||
* @param User $user the current user
|
||||
*/
|
||||
function saveStandardProfileDetails($user)
|
||||
{
|
||||
$fullname = $this->trimmed('extprofile-fullname');
|
||||
$location = $this->trimmed('extprofile-location');
|
||||
$tagstring = $this->trimmed('extprofile-tags');
|
||||
$bio = $this->trimmed('extprofile-bio');
|
||||
|
||||
if ($tagstring) {
|
||||
$tags = array_map(
|
||||
'common_canonical_tag',
|
||||
preg_split('/[\s,]+/', $tagstring)
|
||||
);
|
||||
} else {
|
||||
$tags = array();
|
||||
}
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if (!common_valid_profile_tag($tag)) {
|
||||
// TRANS: Validation error in form for profile settings.
|
||||
// TRANS: %s is an invalid tag.
|
||||
throw new Exception(sprintf(_m('Invalid tag: "%s".'), $tag));
|
||||
}
|
||||
}
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
$oldTags = $user->getSelfTags();
|
||||
$newTags = array_diff($tags, $oldTags);
|
||||
|
||||
if ($fullname != $profile->fullname
|
||||
|| $location != $profile->location
|
||||
|| !empty($newTags)
|
||||
|| $bio != $profile->bio) {
|
||||
|
||||
$orig = clone($profile);
|
||||
|
||||
$profile->nickname = $user->nickname;
|
||||
$profile->fullname = $fullname;
|
||||
$profile->bio = $bio;
|
||||
$profile->location = $location;
|
||||
|
||||
$loc = Location::fromName($location);
|
||||
|
||||
if (empty($loc)) {
|
||||
$profile->lat = null;
|
||||
$profile->lon = null;
|
||||
$profile->location_id = null;
|
||||
$profile->location_ns = null;
|
||||
} else {
|
||||
$profile->lat = $loc->lat;
|
||||
$profile->lon = $loc->lon;
|
||||
$profile->location_id = $loc->location_id;
|
||||
$profile->location_ns = $loc->location_ns;
|
||||
}
|
||||
|
||||
$profile->profileurl = common_profile_url($user->nickname);
|
||||
|
||||
$result = $profile->update($orig);
|
||||
|
||||
if ($result === false) {
|
||||
common_log_db_error($profile, 'UPDATE', __FILE__);
|
||||
// TRANS: Server error thrown when user profile settings could not be saved.
|
||||
$this->serverError(_('Could not save profile.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the user tags
|
||||
$result = $user->setSelfTags($tags);
|
||||
|
||||
if (!$result) {
|
||||
// TRANS: Server error thrown when user profile settings tags could not be saved.
|
||||
$this->serverError(_('Could not save tags.'));
|
||||
return;
|
||||
}
|
||||
|
||||
Event::handle('EndProfileSaveForm', array($this));
|
||||
common_broadcast_profile($profile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,12 @@
|
||||
As of StatusNet 1.0.x, actual formatting of the notices is done server-side,
|
||||
loaded by AJAX after the real-time notification comes in. This has the drawback
|
||||
that we may make extra HTTP requests and delay incoming notices a little, but
|
||||
means that formatting and internationalization is consistent.
|
||||
|
||||
== TODO ==
|
||||
* i18n
|
||||
* Update mark behaviour (on notice send)
|
||||
* Pause, Send a notice ~ should not update counter
|
||||
* Pause ~ retain up to 50-100 most recent notices
|
||||
* Add geo data
|
||||
* Make it work for Conversation page (perhaps a little tricky)
|
||||
* IE is updating the counter in document title all the time (Not sure if this
|
||||
is still an issue)
|
||||
|
@@ -45,9 +45,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
*/
|
||||
class RealtimePlugin extends Plugin
|
||||
{
|
||||
protected $replyurl = null;
|
||||
protected $favorurl = null;
|
||||
protected $deleteurl = null;
|
||||
protected $showurl = null;
|
||||
|
||||
/**
|
||||
* When it's time to initialize the plugin, calculate and
|
||||
@@ -56,11 +54,8 @@ class RealtimePlugin extends Plugin
|
||||
|
||||
function onInitializePlugin()
|
||||
{
|
||||
$this->replyurl = common_local_url('newnotice');
|
||||
$this->favorurl = common_local_url('favor');
|
||||
$this->repeaturl = common_local_url('repeat');
|
||||
// FIXME: need to find a better way to pass this pattern in
|
||||
$this->deleteurl = common_local_url('deletenotice',
|
||||
$this->showurl = common_local_url('shownotice',
|
||||
array('notice' => '0000000000'));
|
||||
return true;
|
||||
}
|
||||
@@ -323,7 +318,12 @@ class RealtimePlugin extends Plugin
|
||||
|
||||
function _getScripts()
|
||||
{
|
||||
return array(Plugin::staticPath('Realtime', 'realtimeupdate.min.js'));
|
||||
if (common_config('site', 'minify')) {
|
||||
$js = 'realtimeupdate.min.js';
|
||||
} else {
|
||||
$js = 'realtimeupdate.js';
|
||||
}
|
||||
return array(Plugin::staticPath('Realtime', $js));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +354,7 @@ class RealtimePlugin extends Plugin
|
||||
|
||||
function _updateInitialize($timeline, $user_id)
|
||||
{
|
||||
return "RealtimeUpdate.init($user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->repeaturl\", \"$this->deleteurl\"); ";
|
||||
return "RealtimeUpdate.init($user_id, \"$this->showurl\"); ";
|
||||
}
|
||||
|
||||
function _connect()
|
||||
|
@@ -44,10 +44,7 @@
|
||||
*/
|
||||
RealtimeUpdate = {
|
||||
_userid: 0,
|
||||
_replyurl: '',
|
||||
_favorurl: '',
|
||||
_repeaturl: '',
|
||||
_deleteurl: '',
|
||||
_showurl: '',
|
||||
_updatecounter: 0,
|
||||
_maxnotices: 50,
|
||||
_windowhasfocus: true,
|
||||
@@ -66,21 +63,15 @@ RealtimeUpdate = {
|
||||
* feed data into the RealtimeUpdate object!
|
||||
*
|
||||
* @param {int} userid: local profile ID of the currently logged-in user
|
||||
* @param {String} replyurl: URL for newnotice action, used when generating reply buttons
|
||||
* @param {String} favorurl: URL for favor action, used when generating fave buttons
|
||||
* @param {String} repeaturl: URL for repeat action, used when generating repeat buttons
|
||||
* @param {String} deleteurl: URL template for deletenotice action, used when generating delete buttons.
|
||||
* @param {String} showurl: URL for shownotice action, used when fetching formatting notices.
|
||||
* This URL contains a stub value of 0000000000 which will be replaced with the notice ID.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
init: function(userid, replyurl, favorurl, repeaturl, deleteurl)
|
||||
init: function(userid, showurl)
|
||||
{
|
||||
RealtimeUpdate._userid = userid;
|
||||
RealtimeUpdate._replyurl = replyurl;
|
||||
RealtimeUpdate._favorurl = favorurl;
|
||||
RealtimeUpdate._repeaturl = repeaturl;
|
||||
RealtimeUpdate._deleteurl = deleteurl;
|
||||
RealtimeUpdate._showurl = showurl;
|
||||
|
||||
RealtimeUpdate._documenttitle = document.title;
|
||||
|
||||
@@ -163,50 +154,51 @@ RealtimeUpdate = {
|
||||
return;
|
||||
}
|
||||
|
||||
var noticeItem = RealtimeUpdate.makeNoticeItem(data);
|
||||
var noticeItemID = $(noticeItem).attr('id');
|
||||
RealtimeUpdate.makeNoticeItem(data, function(noticeItem) {
|
||||
var noticeItemID = $(noticeItem).attr('id');
|
||||
|
||||
var list = $("#notices_primary .notices:first")
|
||||
var prepend = true;
|
||||
var list = $("#notices_primary .notices:first")
|
||||
var prepend = true;
|
||||
|
||||
var threaded = list.hasClass('threaded-notices');
|
||||
if (threaded && data.in_reply_to_status_id) {
|
||||
// aho!
|
||||
var parent = $('#notice-' + data.in_reply_to_status_id);
|
||||
if (parent.length == 0) {
|
||||
// @todo fetch the original, insert it, and finish the rest
|
||||
} else {
|
||||
// Check the parent notice to make sure it's not a reply itself.
|
||||
// If so, use it's parent as the parent.
|
||||
var parentList = parent.closest('.notices');
|
||||
if (parentList.hasClass('threaded-replies')) {
|
||||
parent = parentList.closest('.notice');
|
||||
var threaded = list.hasClass('threaded-notices');
|
||||
if (threaded && data.in_reply_to_status_id) {
|
||||
// aho!
|
||||
var parent = $('#notice-' + data.in_reply_to_status_id);
|
||||
if (parent.length == 0) {
|
||||
// @todo fetch the original, insert it, and finish the rest
|
||||
} else {
|
||||
// Check the parent notice to make sure it's not a reply itself.
|
||||
// If so, use it's parent as the parent.
|
||||
var parentList = parent.closest('.notices');
|
||||
if (parentList.hasClass('threaded-replies')) {
|
||||
parent = parentList.closest('.notice');
|
||||
}
|
||||
list = parent.find('.threaded-replies');
|
||||
if (list.length == 0) {
|
||||
list = $('<ul class="notices threaded-replies xoxo"></ul>');
|
||||
parent.append(list);
|
||||
}
|
||||
prepend = false;
|
||||
}
|
||||
list = parent.find('.threaded-replies');
|
||||
if (list.length == 0) {
|
||||
list = $('<ul class="notices threaded-replies xoxo"></ul>');
|
||||
parent.append(list);
|
||||
}
|
||||
prepend = false;
|
||||
}
|
||||
}
|
||||
|
||||
var newNotice = $(noticeItem);
|
||||
if (prepend) {
|
||||
list.prepend(newNotice);
|
||||
} else {
|
||||
var placeholder = list.find('li.notice-reply-placeholder')
|
||||
if (placeholder.length > 0) {
|
||||
newNotice.insertBefore(placeholder)
|
||||
var newNotice = $(noticeItem);
|
||||
if (prepend) {
|
||||
list.prepend(newNotice);
|
||||
} else {
|
||||
newNotice.appendTo(list);
|
||||
SN.U.NoticeInlineReplyPlaceholder(parent);
|
||||
var placeholder = list.find('li.notice-reply-placeholder')
|
||||
if (placeholder.length > 0) {
|
||||
newNotice.insertBefore(placeholder)
|
||||
} else {
|
||||
newNotice.appendTo(list);
|
||||
SN.U.NoticeInlineReplyPlaceholder(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
newNotice.css({display:"none"}).fadeIn(1000);
|
||||
newNotice.css({display:"none"}).fadeIn(1000);
|
||||
|
||||
SN.U.NoticeReplyTo($('#'+noticeItemID));
|
||||
SN.U.NoticeWithAttachment($('#'+noticeItemID));
|
||||
SN.U.NoticeReplyTo($('#'+noticeItemID));
|
||||
SN.U.NoticeWithAttachment($('#'+noticeItemID));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -263,86 +255,24 @@ RealtimeUpdate = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds a notice HTML block from JSON API-style data.
|
||||
* Builds a notice HTML block from JSON API-style data;
|
||||
* loads data from server, so runs async.
|
||||
*
|
||||
* @param {Object} data: extended JSON API-formatted notice
|
||||
* @return {String} HTML fragment
|
||||
*
|
||||
* @fixme this replicates core StatusNet code, making maintenance harder
|
||||
* @fixme sloppy HTML building (raw concat without escaping)
|
||||
* @fixme no i18n support
|
||||
* @fixme local variables pollute global namespace
|
||||
* @param {function} callback: function(DOMNode) to receive new code
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
makeNoticeItem: function(data)
|
||||
makeNoticeItem: function(data, callback)
|
||||
{
|
||||
if (data.hasOwnProperty('retweeted_status')) {
|
||||
original = data['retweeted_status'];
|
||||
repeat = data;
|
||||
data = original;
|
||||
unique = repeat['id'];
|
||||
responsible = repeat['user'];
|
||||
} else {
|
||||
original = null;
|
||||
repeat = null;
|
||||
unique = data['id'];
|
||||
responsible = data['user'];
|
||||
}
|
||||
|
||||
user = data['user'];
|
||||
html = data['html'].replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/&/g,'&');
|
||||
source = data['source'].replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/&/g,'&');
|
||||
|
||||
ni = "<li class=\"hentry notice\" id=\"notice-"+unique+"\">"+
|
||||
"<div class=\"entry-title\">"+
|
||||
"<span class=\"vcard author\">"+
|
||||
"<a href=\""+user['profile_url']+"\" class=\"url\" title=\""+user['name']+"\">"+
|
||||
"<img src=\""+user['profile_image_url']+"\" class=\"avatar photo\" width=\"48\" height=\"48\" alt=\""+user['screen_name']+"\"/>"+
|
||||
"<span class=\"nickname fn\">"+user['screen_name']+"</span>"+
|
||||
"</a>"+
|
||||
"</span>"+
|
||||
"<p class=\"entry-content\">"+html+"</p>"+
|
||||
"</div>"+
|
||||
"<div class=\"entry-content\">"+
|
||||
"<a class=\"timestamp\" rel=\"bookmark\" href=\""+data['url']+"\" >"+
|
||||
"<abbr class=\"published\" title=\""+data['created_at']+"\">a few seconds ago</abbr>"+
|
||||
"</a> "+
|
||||
"<span class=\"source\">"+
|
||||
"from "+
|
||||
"<span class=\"device\">"+source+"</span>"+ // may have a link
|
||||
"</span>";
|
||||
if (data['conversation_url']) {
|
||||
ni = ni+" <a class=\"response\" href=\""+data['conversation_url']+"\">in context</a>";
|
||||
}
|
||||
|
||||
if (repeat) {
|
||||
ru = repeat['user'];
|
||||
ni = ni + "<span class=\"repeat vcard\">Repeated by " +
|
||||
"<a href=\"" + ru['profile_url'] + "\" class=\"url\">" +
|
||||
"<span class=\"nickname\">"+ ru['screen_name'] + "</span></a></span>";
|
||||
}
|
||||
|
||||
ni = ni+"</div>";
|
||||
|
||||
ni = ni + "<div class=\"notice-options\">";
|
||||
|
||||
if (RealtimeUpdate._userid != 0) {
|
||||
var input = $("form#form_notice fieldset input#token");
|
||||
var session_key = input.val();
|
||||
ni = ni+RealtimeUpdate.makeFavoriteForm(data['id'], session_key);
|
||||
ni = ni+RealtimeUpdate.makeReplyLink(data['id'], data['user']['screen_name']);
|
||||
if (RealtimeUpdate._userid == responsible['id']) {
|
||||
ni = ni+RealtimeUpdate.makeDeleteLink(data['id']);
|
||||
} else if (RealtimeUpdate._userid != user['id']) {
|
||||
ni = ni+RealtimeUpdate.makeRepeatForm(data['id'], session_key);
|
||||
}
|
||||
}
|
||||
|
||||
ni = ni+"</div>";
|
||||
|
||||
ni = ni+"</li>";
|
||||
return ni;
|
||||
var url = RealtimeUpdate._showurl.replace('0000000000', data.id);
|
||||
$.get(url, {ajax: 1}, function(data, textStatus, xhr) {
|
||||
var notice = $('li.notice:first', data);
|
||||
if (notice.length) {
|
||||
var node = document._importNode(notice[0], true);
|
||||
callback(node);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
2
plugins/Realtime/realtimeupdate.min.js
vendored
2
plugins/Realtime/realtimeupdate.min.js
vendored
File diff suppressed because one or more lines are too long
140
plugins/SearchSub/SearchSub.php
Normal file
140
plugins/SearchSub/SearchSub.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* Data class to store local search subscriptions
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2011, StatusNet, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* For storing the search subscriptions
|
||||
*
|
||||
* @category PollPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*
|
||||
* @see DB_DataObject
|
||||
*/
|
||||
|
||||
class SearchSub extends Managed_DataObject
|
||||
{
|
||||
public $__table = 'searchsub'; // table name
|
||||
public $search; // text
|
||||
public $profile_id; // int -> profile.id
|
||||
public $created; // datetime
|
||||
|
||||
/**
|
||||
* Get an instance by key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given key value.
|
||||
*
|
||||
* @param string $k Key to use to lookup (usually 'user_id' for this class)
|
||||
* @param mixed $v Value to lookup
|
||||
*
|
||||
* @return SearchSub object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
function staticGet($k, $v=null)
|
||||
{
|
||||
return Memcached_DataObject::staticGet('SearchSub', $k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance by compound key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given set of
|
||||
* key-value pairs. Usually used for the primary key for a compound key; thus
|
||||
* the name.
|
||||
*
|
||||
* @param array $kv array of key-value mappings
|
||||
*
|
||||
* @return SearchSub object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
function pkeyGet($kv)
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('SearchSub', $kv);
|
||||
}
|
||||
|
||||
/**
|
||||
* The One True Thingy that must be defined and declared.
|
||||
*/
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
'description' => 'SearchSubPlugin search subscription records',
|
||||
'fields' => array(
|
||||
'search' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash search associated with this subscription'),
|
||||
'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile ID of subscribing user'),
|
||||
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
|
||||
),
|
||||
'primary key' => array('search', 'profile_id'),
|
||||
'foreign keys' => array(
|
||||
'searchsub_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
|
||||
),
|
||||
'indexes' => array(
|
||||
'searchsub_created_idx' => array('created'),
|
||||
'searchsub_profile_id_tag_idx' => array('profile_id', 'search'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search subscription!
|
||||
*
|
||||
* @param profile $profile subscriber
|
||||
* @param string $search subscribee
|
||||
* @return SearchSub
|
||||
*/
|
||||
static function start(Profile $profile, $search)
|
||||
{
|
||||
$ts = new SearchSub();
|
||||
$ts->search = $search;
|
||||
$ts->profile_id = $profile->id;
|
||||
$ts->created = common_sql_now();
|
||||
$ts->insert();
|
||||
return $ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* End a search subscription!
|
||||
*
|
||||
* @param profile $profile subscriber
|
||||
* @param string $search subscribee
|
||||
*/
|
||||
static function cancel(Profile $profile, $search)
|
||||
{
|
||||
$ts = SearchSub::pkeyGet(array('search' => $search,
|
||||
'profile_id' => $profile->id));
|
||||
if ($ts) {
|
||||
$ts->delete();
|
||||
}
|
||||
}
|
||||
}
|
212
plugins/SearchSub/SearchSubPlugin.php
Normal file
212
plugins/SearchSub/SearchSubPlugin.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2011, StatusNet, Inc.
|
||||
*
|
||||
* A plugin to enable local tab subscription
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchSub plugin main class
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brionv@status.net>
|
||||
* @copyright 2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class SearchSubPlugin extends Plugin
|
||||
{
|
||||
const VERSION = '0.1';
|
||||
|
||||
/**
|
||||
* Database schema setup
|
||||
*
|
||||
* @see Schema
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
function onCheckSchema()
|
||||
{
|
||||
$schema = Schema::get();
|
||||
$schema->ensureTable('searchsub', SearchSub::schemaDef());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load related modules when needed
|
||||
*
|
||||
* @param string $cls Name of the class to be loaded
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
function onAutoload($cls)
|
||||
{
|
||||
$dir = dirname(__FILE__);
|
||||
|
||||
switch ($cls)
|
||||
{
|
||||
case 'SearchSub':
|
||||
include_once $dir.'/'.$cls.'.php';
|
||||
return false;
|
||||
case 'SearchsubAction':
|
||||
case 'SearchunsubAction':
|
||||
case 'SearchSubForm':
|
||||
case 'SearchUnsubForm':
|
||||
include_once $dir.'/'.strtolower($cls).'.php';
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map URLs to actions
|
||||
*
|
||||
* @param Net_URL_Mapper $m path-to-action mapper
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
function onRouterInitialized($m)
|
||||
{
|
||||
$m->connect('search/:search/subscribe',
|
||||
array('action' => 'searchsub'),
|
||||
array('search' => Router::REGEX_TAG));
|
||||
$m->connect('search/:search/unsubscribe',
|
||||
array('action' => 'searchunsub'),
|
||||
array('search' => Router::REGEX_TAG));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin version data
|
||||
*
|
||||
* @param array &$versions array of version data
|
||||
*
|
||||
* @return value
|
||||
*/
|
||||
function onPluginVersion(&$versions)
|
||||
{
|
||||
$versions[] = array('name' => 'SearchSub',
|
||||
'version' => self::VERSION,
|
||||
'author' => 'Brion Vibber',
|
||||
'homepage' => 'http://status.net/wiki/Plugin:SearchSub',
|
||||
'rawdescription' =>
|
||||
// TRANS: Plugin description.
|
||||
_m('Plugin to allow following all messages with a given search.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook inbox delivery setup so search subscribers receive all
|
||||
* notices with that search in their inbox.
|
||||
*
|
||||
* Currently makes no distinction between local messages and
|
||||
* remote ones which happen to come in to the system. Remote
|
||||
* notices that don't come in at all won't ever reach this.
|
||||
*
|
||||
* @param Notice $notice
|
||||
* @param array $ni in/out map of profile IDs to inbox constants
|
||||
* @return boolean hook result
|
||||
*/
|
||||
function onStartNoticeWhoGets(Notice $notice, array &$ni)
|
||||
{
|
||||
// Warning: this is potentially very slow
|
||||
// with a lot of searches!
|
||||
$sub = new SearchSub();
|
||||
$sub->groupBy('search');
|
||||
$sub->find();
|
||||
while ($sub->fetch()) {
|
||||
$search = $sub->search;
|
||||
|
||||
if ($this->matchSearch($notice, $search)) {
|
||||
// Match? Find all those who subscribed to this
|
||||
// search term and get our delivery on...
|
||||
$searchsub = new SearchSub();
|
||||
$searchsub->search = $search;
|
||||
$searchsub->find();
|
||||
|
||||
while ($searchsub->fetch()) {
|
||||
// These constants are currently not actually used, iirc
|
||||
$ni[$searchsub->profile_id] = NOTICE_INBOX_SOURCE_SUB;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given notice match the given fulltext search query?
|
||||
*
|
||||
* Warning: not guaranteed to match other search engine behavior, etc.
|
||||
* Currently using a basic case-insensitive substring match, which
|
||||
* probably fits with the 'LIKE' search but not the default MySQL
|
||||
* or Sphinx search backends.
|
||||
*
|
||||
* @param Notice $notice
|
||||
* @param string $search
|
||||
* @return boolean
|
||||
*/
|
||||
function matchSearch(Notice $notice, $search)
|
||||
{
|
||||
return (mb_stripos($notice->content, $search) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param NoticeSearchAction $action
|
||||
* @param string $q
|
||||
* @param Notice $notice
|
||||
* @return boolean hook result
|
||||
*/
|
||||
function onStartNoticeSearchShowResults($action, $q, $notice)
|
||||
{
|
||||
$user = common_current_user();
|
||||
if ($user) {
|
||||
$search = $q;
|
||||
$searchsub = SearchSub::pkeyGet(array('search' => $search,
|
||||
'profile_id' => $user->id));
|
||||
if ($searchsub) {
|
||||
$form = new SearchUnsubForm($action, $search);
|
||||
} else {
|
||||
$form = new SearchSubForm($action, $search);
|
||||
}
|
||||
$action->elementStart('div', 'entity_actions');
|
||||
$action->elementStart('ul');
|
||||
$action->elementStart('li', 'entity_subscribe');
|
||||
$form->show();
|
||||
$action->elementEnd('li');
|
||||
$action->elementEnd('ul');
|
||||
$action->elementEnd('div');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
149
plugins/SearchSub/searchsubaction.php
Normal file
149
plugins/SearchSub/searchsubaction.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008-2011, StatusNet, Inc.
|
||||
*
|
||||
* Search subscription action.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search subscription action
|
||||
*
|
||||
* Takes parameters:
|
||||
*
|
||||
* - 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 Evan Prodromou <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2008-2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class SearchsubAction extends Action
|
||||
{
|
||||
var $user;
|
||||
var $search;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if ($this->boolean('ajax')) {
|
||||
StatusNet::setApi(true);
|
||||
}
|
||||
|
||||
// Only allow POST requests
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
// TRANS: Client error displayed trying to perform any request method other than POST.
|
||||
// TRANS: Do not translate POST.
|
||||
$this->clientError(_('This action only accepts POST requests.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// CSRF protection
|
||||
|
||||
$token = $this->trimmed('token');
|
||||
|
||||
if (!$token || $token != common_session_token()) {
|
||||
// TRANS: Client error displayed when the session token is not okay.
|
||||
$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)) {
|
||||
// TRANS: Client error displayed trying to subscribe when not logged in.
|
||||
$this->clientError(_('Not logged in.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Profile to subscribe to
|
||||
|
||||
$this->search = $this->arg('search');
|
||||
|
||||
if (empty($this->search)) {
|
||||
// TRANS: Client error displayed trying to subscribe to a non-existing profile.
|
||||
$this->clientError(_('No such profile.'));
|
||||
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
|
||||
|
||||
SearchSub::start($this->user->getProfile(),
|
||||
$this->search);
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->startHTML('text/xml;charset=utf-8');
|
||||
$this->elementStart('head');
|
||||
// TRANS: Page title when search subscription succeeded.
|
||||
$this->element('title', null, _m('Subscribed'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$unsubscribe = new SearchUnsubForm($this, $this->search);
|
||||
$unsubscribe->show();
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
$url = common_local_url('search',
|
||||
array('search' => $this->search));
|
||||
common_redirect($url, 303);
|
||||
}
|
||||
}
|
||||
}
|
142
plugins/SearchSub/searchsubform.php
Normal file
142
plugins/SearchSub/searchsubform.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Form for subscribing to a search
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @copyright 2009-2011 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for subscribing to a user
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*
|
||||
* @see UnsubscribeForm
|
||||
*/
|
||||
|
||||
class SearchSubForm extends Form
|
||||
{
|
||||
/**
|
||||
* Name of search to subscribe to
|
||||
*/
|
||||
|
||||
var $search = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param HTMLOutputter $out output channel
|
||||
* @param string $search name of search to subscribe to
|
||||
*/
|
||||
|
||||
function __construct($out=null, $search=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
|
||||
$this->search = $search;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* @return int ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'search-subscribe-' . $this->search;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class of the form
|
||||
*
|
||||
* @return string of the form class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
// class to match existing styles...
|
||||
return 'form_user_subscribe ajax';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action of the form
|
||||
*
|
||||
* @return string URL of the action
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('searchsub', array('search' => $this->search));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Legend of the Form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formLegend()
|
||||
{
|
||||
$this->out->element('legend', null, _m('Subscribe to this search'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data elements of the form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formData()
|
||||
{
|
||||
$this->out->hidden('subscribeto-' . $this->search,
|
||||
$this->search,
|
||||
'subscribeto');
|
||||
}
|
||||
|
||||
/**
|
||||
* Action elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit('submit', _('Subscribe'), 'submit', null, _m('Subscribe to this search'));
|
||||
}
|
||||
}
|
89
plugins/SearchSub/searchunsubaction.php
Normal file
89
plugins/SearchSub/searchunsubaction.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008-2011, StatusNet, Inc.
|
||||
*
|
||||
* Search subscription action.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search unsubscription action
|
||||
*
|
||||
* Takes parameters:
|
||||
*
|
||||
* - 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 Evan Prodromou <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2008-2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class SearchunsubAction extends SearchsubAction
|
||||
{
|
||||
/**
|
||||
* Handle request
|
||||
*
|
||||
* Does the subscription and returns results.
|
||||
*
|
||||
* @param Array $args unused.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
// Throws exception on error
|
||||
|
||||
SearchSub::cancel($this->user->getProfile(),
|
||||
$this->search);
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->startHTML('text/xml;charset=utf-8');
|
||||
$this->elementStart('head');
|
||||
// TRANS: Page title when search unsubscription succeeded.
|
||||
$this->element('title', null, _m('Unsubscribed'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$subscribe = new SearchSubForm($this, $this->search);
|
||||
$subscribe->show();
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
$url = common_local_url('search',
|
||||
array('search' => $this->search));
|
||||
common_redirect($url, 303);
|
||||
}
|
||||
}
|
||||
}
|
109
plugins/SearchSub/searchunsubform.php
Normal file
109
plugins/SearchSub/searchunsubform.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Form for subscribing to a search
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @copyright 2009-2011 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for subscribing to a user
|
||||
*
|
||||
* @category SearchSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*
|
||||
* @see UnsubscribeForm
|
||||
*/
|
||||
|
||||
class SearchUnsubForm extends SearchSubForm
|
||||
{
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* @return int ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'search-unsubscribe-' . $this->search;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class of the form
|
||||
*
|
||||
* @return string of the form class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
// class to match existing styles...
|
||||
return 'form_user_unsubscribe ajax';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action of the form
|
||||
*
|
||||
* @return string URL of the action
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('searchunsub', array('search' => $this->search));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Legend of the Form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formLegend()
|
||||
{
|
||||
$this->out->element('legend', null, _m('Unsubscribe from this search'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit('submit', _('Unsubscribe'), 'submit', null, _m('Unsubscribe from this search'));
|
||||
}
|
||||
}
|
@@ -35,6 +35,9 @@ class SubMirrorPlugin extends Plugin
|
||||
{
|
||||
$m->connect('settings/mirror',
|
||||
array('action' => 'mirrorsettings'));
|
||||
$m->connect('settings/mirror/add/:provider',
|
||||
array('action' => 'mirrorsettings'),
|
||||
array('provider' => '[A-Za-z0-9_-]+'));
|
||||
$m->connect('settings/mirror/add',
|
||||
array('action' => 'addmirror'));
|
||||
$m->connect('settings/mirror/edit',
|
||||
|
@@ -59,11 +59,27 @@ class AddMirrorAction extends BaseMirrorAction
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
$this->feedurl = $this->validateFeedUrl($this->trimmed('feedurl'));
|
||||
$feedurl = $this->getFeedUrl();
|
||||
$this->feedurl = $this->validateFeedUrl($feedurl);
|
||||
$this->profile = $this->profileForFeed($this->feedurl);
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFeedUrl()
|
||||
{
|
||||
$provider = $this->trimmed('provider');
|
||||
switch ($provider) {
|
||||
case 'feed':
|
||||
return $this->trimmed('feedurl');
|
||||
case 'twitter':
|
||||
$screenie = $this->trimmed('screen_name');
|
||||
$base = 'http://api.twitter.com/1/statuses/user_timeline.atom?screen_name=';
|
||||
return $base . urlencode($screenie);
|
||||
default:
|
||||
throw new Exception('Internal form error: unrecognized feed provider.');
|
||||
}
|
||||
}
|
||||
|
||||
function saveMirror()
|
||||
{
|
||||
if ($this->oprofile->subscribe()) {
|
||||
|
@@ -68,7 +68,7 @@ abstract class BaseMirrorAction extends Action
|
||||
if (common_valid_http_url($url)) {
|
||||
return $url;
|
||||
} else {
|
||||
$this->clientError(_m("Invalid feed URL."));
|
||||
$this->clientError(sprintf(_m("Invalid feed URL: %s"), $url));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -65,18 +65,30 @@ class MirrorSettingsAction extends SettingsAction
|
||||
function showContent()
|
||||
{
|
||||
$user = common_current_user();
|
||||
$provider = $this->trimmed('provider');
|
||||
if ($provider) {
|
||||
$this->showAddFeedForm($provider);
|
||||
} else {
|
||||
$this->elementStart('div', array('id' => 'add-mirror'));
|
||||
$this->showAddWizard();
|
||||
$this->elementEnd('div');
|
||||
|
||||
$this->showAddFeedForm();
|
||||
|
||||
$mirror = new SubMirror();
|
||||
$mirror->subscriber = $user->id;
|
||||
if ($mirror->find()) {
|
||||
while ($mirror->fetch()) {
|
||||
$this->showFeedForm($mirror);
|
||||
$mirror = new SubMirror();
|
||||
$mirror->subscriber = $user->id;
|
||||
if ($mirror->find()) {
|
||||
while ($mirror->fetch()) {
|
||||
$this->showFeedForm($mirror);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showAddWizard()
|
||||
{
|
||||
$form = new AddMirrorWizard($this);
|
||||
$form->show();
|
||||
}
|
||||
|
||||
function showFeedForm($mirror)
|
||||
{
|
||||
$profile = Profile::staticGet('id', $mirror->subscribed);
|
||||
@@ -88,10 +100,47 @@ class MirrorSettingsAction extends SettingsAction
|
||||
|
||||
function showAddFeedForm()
|
||||
{
|
||||
$form = new AddMirrorForm($this);
|
||||
switch ($this->arg('provider')) {
|
||||
case 'statusnet':
|
||||
break;
|
||||
case 'twitter':
|
||||
$form = new AddTwitterMirrorForm($this);
|
||||
break;
|
||||
case 'wordpress':
|
||||
break;
|
||||
case 'linkedin':
|
||||
break;
|
||||
case 'feed':
|
||||
default:
|
||||
$form = new AddMirrorForm($this);
|
||||
}
|
||||
$form->show();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $args
|
||||
*
|
||||
* @todo move the ajax display handling to common code
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
if ($this->boolean('ajax')) {
|
||||
header('Content-Type: text/html;charset=utf-8');
|
||||
$this->elementStart('html');
|
||||
$this->elementStart('head');
|
||||
$this->element('title', null, _('Provider add'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
|
||||
$this->showAddFeedForm();
|
||||
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
return parent::handle($args);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle a POST request
|
||||
*
|
||||
@@ -108,4 +157,16 @@ class MirrorSettingsAction extends SettingsAction
|
||||
$nav = new SubGroupNav($this, common_current_user());
|
||||
$nav->show();
|
||||
}
|
||||
|
||||
function showScripts()
|
||||
{
|
||||
parent::showScripts();
|
||||
$this->script('plugins/SubMirror/js/mirrorsettings.js');
|
||||
}
|
||||
|
||||
function showStylesheets()
|
||||
{
|
||||
parent::showStylesheets();
|
||||
$this->cssLink('plugins/SubMirror/css/mirrorsettings.css');
|
||||
}
|
||||
}
|
||||
|
26
plugins/SubMirror/css/mirrorsettings.css
Normal file
26
plugins/SubMirror/css/mirrorsettings.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/* undo insane stuff from core styles */
|
||||
#add-mirror-wizard img {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* we need #something to override most of the #content crap */
|
||||
|
||||
#add-mirror-wizard {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#add-mirror-wizard .provider-list table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#add-mirror-wizard .provider-heading img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#add-mirror-wizard .provider-heading {
|
||||
cursor: pointer;
|
||||
}
|
||||
#add-mirror-wizard .provider-detail fieldset {
|
||||
margin-top: 8px; /* hack */
|
||||
margin-bottom: 8px; /* hack */
|
||||
}
|
BIN
plugins/SubMirror/images/providers/facebook.png
Normal file
BIN
plugins/SubMirror/images/providers/facebook.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 958 B |
BIN
plugins/SubMirror/images/providers/feed.png
Normal file
BIN
plugins/SubMirror/images/providers/feed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
plugins/SubMirror/images/providers/linkedin.png
Normal file
BIN
plugins/SubMirror/images/providers/linkedin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
plugins/SubMirror/images/providers/statusnet.png
Normal file
BIN
plugins/SubMirror/images/providers/statusnet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
BIN
plugins/SubMirror/images/providers/twitter.png
Normal file
BIN
plugins/SubMirror/images/providers/twitter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
plugins/SubMirror/images/providers/wordpress.png
Normal file
BIN
plugins/SubMirror/images/providers/wordpress.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
47
plugins/SubMirror/js/mirrorsettings.js
Normal file
47
plugins/SubMirror/js/mirrorsettings.js
Normal file
@@ -0,0 +1,47 @@
|
||||
$(function() {
|
||||
/**
|
||||
* Append 'ajax=1' parameter onto URL.
|
||||
*/
|
||||
function ajaxize(url) {
|
||||
if (url.indexOf('?') == '-1') {
|
||||
return url + '?ajax=1';
|
||||
} else {
|
||||
return url + '&ajax=1';
|
||||
}
|
||||
}
|
||||
|
||||
var addMirror = $('#add-mirror');
|
||||
var wizard = $('#add-mirror-wizard');
|
||||
if (wizard.length > 0) {
|
||||
var list = wizard.find('.provider-list');
|
||||
var providers = list.find('.provider-heading');
|
||||
providers.click(function(event) {
|
||||
console.log(this);
|
||||
var targetUrl = $(this).find('a').attr('href');
|
||||
if (targetUrl) {
|
||||
// Make sure we don't accidentally follow the direct link
|
||||
event.preventDefault();
|
||||
|
||||
var node = this;
|
||||
function showNew() {
|
||||
var detail = $('<div class="provider-detail" style="display: none"></div>').insertAfter(node);
|
||||
detail.load(ajaxize(targetUrl), function(responseText, testStatus, xhr) {
|
||||
detail.slideDown('fast', function() {
|
||||
detail.find('input[type="text"]').focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var old = addMirror.find('.provider-detail');
|
||||
if (old.length) {
|
||||
old.slideUp('fast', function() {
|
||||
old.remove();
|
||||
showNew();
|
||||
});
|
||||
} else {
|
||||
showNew();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@@ -49,6 +49,7 @@ class AddMirrorForm extends Form
|
||||
*/
|
||||
function formData()
|
||||
{
|
||||
$this->out->hidden('provider', 'feed');
|
||||
$this->out->elementStart('fieldset');
|
||||
|
||||
$this->out->elementStart('ul');
|
||||
@@ -67,7 +68,7 @@ class AddMirrorForm extends Form
|
||||
$this->out->elementEnd('fieldset');
|
||||
}
|
||||
|
||||
private function doInput($id, $name, $label, $value=null, $instructions=null)
|
||||
protected function doInput($id, $name, $label, $value=null, $instructions=null)
|
||||
{
|
||||
$this->out->element('label', array('for' => $id), $label);
|
||||
$attrs = array('name' => $name,
|
||||
|
187
plugins/SubMirror/lib/addmirrorwizard.php
Normal file
187
plugins/SubMirror/lib/addmirrorwizard.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package StatusNet
|
||||
* @copyright 2010-2011 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);
|
||||
}
|
||||
|
||||
class AddMirrorWizard extends Widget
|
||||
{
|
||||
/**
|
||||
* Name of the form
|
||||
*
|
||||
* Sub-classes should overload this with the name of their form.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formLegend()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible or invisible data elements
|
||||
*
|
||||
* Display the form fields that make up the data of the form.
|
||||
* Sub-classes should overload this to show their data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function show()
|
||||
{
|
||||
$this->out->elementStart('div', array('id' => 'add-mirror-wizard'));
|
||||
|
||||
$providers = $this->providers();
|
||||
$this->showProviders($providers);
|
||||
|
||||
$this->out->elementEnd('div');
|
||||
}
|
||||
|
||||
function providers()
|
||||
{
|
||||
return array(
|
||||
/*
|
||||
// We could accept hostname & username combos here, or
|
||||
// webfingery combinations as for remote users.
|
||||
array(
|
||||
'id' => 'statusnet',
|
||||
'name' => _m('StatusNet'),
|
||||
),
|
||||
*/
|
||||
// Accepts a Twitter username and pulls their user timeline as a
|
||||
// public Atom feed. Requires a working alternate hub which, one
|
||||
// hopes, is getting timely updates.
|
||||
array(
|
||||
'id' => 'twitter',
|
||||
'name' => _m('Twitter'),
|
||||
),
|
||||
/*
|
||||
// WordPress was on our list some whiles ago, but not sure
|
||||
// what we can actually do here. Search on Wordpress.com hosted
|
||||
// sites, or ?
|
||||
array(
|
||||
'id' => 'wordpress',
|
||||
'name' => _m('WordPress'),
|
||||
),
|
||||
*/
|
||||
/*
|
||||
// In theory, Facebook lets you pull public updates over RSS,
|
||||
// but the URLs for your own update feed that I can find from
|
||||
// 2009-era websites no longer seem to work and there's no
|
||||
// good current documentation. May not still be available...
|
||||
// Mirroring from an FB account is probably better done with
|
||||
// the dedicated plugin. (As of March 2011)
|
||||
array(
|
||||
'id' => 'facebook',
|
||||
'name' => _m('Facebook'),
|
||||
),
|
||||
*/
|
||||
/*
|
||||
// LinkedIn doesn't currently seem to have public feeds
|
||||
// for users or groups (March 2011)
|
||||
array(
|
||||
'id' => 'linkedin',
|
||||
'name' => _m('LinkedIn'),
|
||||
),
|
||||
*/
|
||||
array(
|
||||
'id' => 'feed',
|
||||
'name' => _m('RSS or Atom feed'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function showProviders(array $providers)
|
||||
{
|
||||
$out = $this->out;
|
||||
|
||||
$out->elementStart('div', 'provider-list');
|
||||
$out->element('h2', null, _m('Select a feed provider'));
|
||||
$out->elementStart('table');
|
||||
foreach ($providers as $provider) {
|
||||
$icon = common_path('plugins/SubMirror/images/providers/' . $provider['id'] . '.png');
|
||||
$targetUrl = common_local_url('mirrorsettings', array('provider' => $provider['id']));
|
||||
|
||||
$out->elementStart('tr', array('class' => 'provider'));
|
||||
$out->elementStart('td');
|
||||
|
||||
$out->elementStart('div', 'provider-heading');
|
||||
$out->element('img', array('src' => $icon));
|
||||
$out->element('a', array('href' => $targetUrl), $provider['name']);
|
||||
$out->elementEnd('div');
|
||||
|
||||
$out->elementEnd('td');
|
||||
$out->elementEnd('tr');
|
||||
}
|
||||
$out->elementEnd('table');
|
||||
$out->elementEnd('div');
|
||||
}
|
||||
|
||||
/**
|
||||
* Buttons for form actions
|
||||
*
|
||||
* Submit and cancel buttons (or whatever)
|
||||
* Sub-classes should overload this to show their own buttons.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formActions()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* Should be unique on the page. Sub-classes should overload this
|
||||
* to show their own IDs.
|
||||
*
|
||||
* @return string ID of the form
|
||||
*/
|
||||
function id()
|
||||
{
|
||||
return 'add-mirror-wizard';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action of the form.
|
||||
*
|
||||
* URL to post to. Should be overloaded by subclasses to give
|
||||
* somewhere to post to.
|
||||
*
|
||||
* @return string URL to post to
|
||||
*/
|
||||
function action()
|
||||
{
|
||||
return common_local_url('addmirror');
|
||||
}
|
||||
|
||||
/**
|
||||
* Class of the form.
|
||||
*
|
||||
* @return string the form's class
|
||||
*/
|
||||
function formClass()
|
||||
{
|
||||
return 'form_settings';
|
||||
}
|
||||
}
|
60
plugins/SubMirror/lib/addtwittermirrorform.php
Normal file
60
plugins/SubMirror/lib/addtwittermirrorform.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package StatusNet
|
||||
* @copyright 2010 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);
|
||||
}
|
||||
|
||||
class AddTwitterMirrorForm extends AddMirrorForm
|
||||
{
|
||||
|
||||
/**
|
||||
* Visible or invisible data elements
|
||||
*
|
||||
* Display the form fields that make up the data of the form.
|
||||
* Sub-classes should overload this to show their data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formData()
|
||||
{
|
||||
$this->out->hidden('provider', 'twitter');
|
||||
$this->out->elementStart('fieldset');
|
||||
|
||||
$this->out->elementStart('ul');
|
||||
|
||||
$this->li();
|
||||
$this->doInput('addmirror-feedurl',
|
||||
'screen_name',
|
||||
_m('Twitter username:'),
|
||||
$this->out->trimmed('screen_name'));
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->submit('addmirror-save', _m('BUTTON','Add feed'));
|
||||
$this->unli();
|
||||
$this->out->elementEnd('ul');
|
||||
$this->out->elementEnd('fieldset');
|
||||
}
|
||||
}
|
140
plugins/TagSub/TagSub.php
Normal file
140
plugins/TagSub/TagSub.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* Data class to store local tag subscriptions
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2011, StatusNet, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* For storing the tag subscriptions
|
||||
*
|
||||
* @category PollPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*
|
||||
* @see DB_DataObject
|
||||
*/
|
||||
|
||||
class TagSub extends Managed_DataObject
|
||||
{
|
||||
public $__table = 'tagsub'; // table name
|
||||
public $tag; // text
|
||||
public $profile_id; // int -> profile.id
|
||||
public $created; // datetime
|
||||
|
||||
/**
|
||||
* Get an instance by key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given key value.
|
||||
*
|
||||
* @param string $k Key to use to lookup (usually 'user_id' for this class)
|
||||
* @param mixed $v Value to lookup
|
||||
*
|
||||
* @return TagSub object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
function staticGet($k, $v=null)
|
||||
{
|
||||
return Memcached_DataObject::staticGet('TagSub', $k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance by compound key
|
||||
*
|
||||
* This is a utility method to get a single instance with a given set of
|
||||
* key-value pairs. Usually used for the primary key for a compound key; thus
|
||||
* the name.
|
||||
*
|
||||
* @param array $kv array of key-value mappings
|
||||
*
|
||||
* @return TagSub object found, or null for no hits
|
||||
*
|
||||
*/
|
||||
function pkeyGet($kv)
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('TagSub', $kv);
|
||||
}
|
||||
|
||||
/**
|
||||
* The One True Thingy that must be defined and declared.
|
||||
*/
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
'description' => 'TagSubPlugin tag subscription records',
|
||||
'fields' => array(
|
||||
'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this subscription'),
|
||||
'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile ID of subscribing user'),
|
||||
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
|
||||
),
|
||||
'primary key' => array('tag', 'profile_id'),
|
||||
'foreign keys' => array(
|
||||
'tagsub_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
|
||||
),
|
||||
'indexes' => array(
|
||||
'tagsub_created_idx' => array('created'),
|
||||
'tagsub_profile_id_tag_idx' => array('profile_id', 'tag'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a tag subscription!
|
||||
*
|
||||
* @param profile $profile subscriber
|
||||
* @param string $tag subscribee
|
||||
* @return TagSub
|
||||
*/
|
||||
static function start(Profile $profile, $tag)
|
||||
{
|
||||
$ts = new TagSub();
|
||||
$ts->tag = $tag;
|
||||
$ts->profile_id = $profile->id;
|
||||
$ts->created = common_sql_now();
|
||||
$ts->insert();
|
||||
return $ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* End a tag subscription!
|
||||
*
|
||||
* @param profile $profile subscriber
|
||||
* @param string $tag subscribee
|
||||
*/
|
||||
static function cancel(Profile $profile, $tag)
|
||||
{
|
||||
$ts = TagSub::pkeyGet(array('tag' => $tag,
|
||||
'profile_id' => $profile->id));
|
||||
if ($ts) {
|
||||
$ts->delete();
|
||||
}
|
||||
}
|
||||
}
|
182
plugins/TagSub/TagSubPlugin.php
Normal file
182
plugins/TagSub/TagSubPlugin.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2011, StatusNet, Inc.
|
||||
*
|
||||
* A plugin to enable local tab subscription
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* TagSub plugin main class
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brionv@status.net>
|
||||
* @copyright 2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class TagSubPlugin extends Plugin
|
||||
{
|
||||
const VERSION = '0.1';
|
||||
|
||||
/**
|
||||
* Database schema setup
|
||||
*
|
||||
* @see Schema
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
function onCheckSchema()
|
||||
{
|
||||
$schema = Schema::get();
|
||||
$schema->ensureTable('tagsub', TagSub::schemaDef());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load related modules when needed
|
||||
*
|
||||
* @param string $cls Name of the class to be loaded
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
function onAutoload($cls)
|
||||
{
|
||||
$dir = dirname(__FILE__);
|
||||
|
||||
switch ($cls)
|
||||
{
|
||||
case 'TagSub':
|
||||
include_once $dir.'/'.$cls.'.php';
|
||||
return false;
|
||||
case 'TagsubAction':
|
||||
case 'TagunsubAction':
|
||||
case 'TagSubForm':
|
||||
case 'TagUnsubForm':
|
||||
include_once $dir.'/'.strtolower($cls).'.php';
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map URLs to actions
|
||||
*
|
||||
* @param Net_URL_Mapper $m path-to-action mapper
|
||||
*
|
||||
* @return boolean hook value; true means continue processing, false means stop.
|
||||
*/
|
||||
function onRouterInitialized($m)
|
||||
{
|
||||
$m->connect('tag/:tag/subscribe',
|
||||
array('action' => 'tagsub'),
|
||||
array('tag' => Router::REGEX_TAG));
|
||||
$m->connect('tag/:tag/unsubscribe',
|
||||
array('action' => 'tagunsub'),
|
||||
array('tag' => Router::REGEX_TAG));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin version data
|
||||
*
|
||||
* @param array &$versions array of version data
|
||||
*
|
||||
* @return value
|
||||
*/
|
||||
function onPluginVersion(&$versions)
|
||||
{
|
||||
$versions[] = array('name' => 'TagSub',
|
||||
'version' => self::VERSION,
|
||||
'author' => 'Brion Vibber',
|
||||
'homepage' => 'http://status.net/wiki/Plugin:TagSub',
|
||||
'rawdescription' =>
|
||||
// TRANS: Plugin description.
|
||||
_m('Plugin to allow following all messages with a given tag.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook inbox delivery setup so tag subscribers receive all
|
||||
* notices with that tag in their inbox.
|
||||
*
|
||||
* Currently makes no distinction between local messages and
|
||||
* remote ones which happen to come in to the system. Remote
|
||||
* notices that don't come in at all won't ever reach this.
|
||||
*
|
||||
* @param Notice $notice
|
||||
* @param array $ni in/out map of profile IDs to inbox constants
|
||||
* @return boolean hook result
|
||||
*/
|
||||
function onStartNoticeWhoGets(Notice $notice, array &$ni)
|
||||
{
|
||||
foreach ($notice->getTags() as $tag) {
|
||||
$tagsub = new TagSub();
|
||||
$tagsub->tag = $tag;
|
||||
$tagsub->find();
|
||||
|
||||
while ($tagsub->fetch()) {
|
||||
// These constants are currently not actually used, iirc
|
||||
$ni[$tagsub->profile_id] = NOTICE_INBOX_SOURCE_SUB;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param TagAction $action
|
||||
* @return boolean hook result
|
||||
*/
|
||||
function onStartTagShowContent(TagAction $action)
|
||||
{
|
||||
$user = common_current_user();
|
||||
if ($user) {
|
||||
$tag = $action->trimmed('tag');
|
||||
$tagsub = TagSub::pkeyGet(array('tag' => $tag,
|
||||
'profile_id' => $user->id));
|
||||
if ($tagsub) {
|
||||
$form = new TagUnsubForm($action, $tag);
|
||||
} else {
|
||||
$form = new TagSubForm($action, $tag);
|
||||
}
|
||||
$action->elementStart('div', 'entity_actions');
|
||||
$action->elementStart('ul');
|
||||
$action->elementStart('li', 'entity_subscribe');
|
||||
$form->show();
|
||||
$action->elementEnd('li');
|
||||
$action->elementEnd('ul');
|
||||
$action->elementEnd('div');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
149
plugins/TagSub/tagsubaction.php
Normal file
149
plugins/TagSub/tagsubaction.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008-2011, StatusNet, Inc.
|
||||
*
|
||||
* Tag subscription action.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag subscription action
|
||||
*
|
||||
* Takes parameters:
|
||||
*
|
||||
* - 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 Evan Prodromou <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2008-2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class TagsubAction extends Action
|
||||
{
|
||||
var $user;
|
||||
var $tag;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if ($this->boolean('ajax')) {
|
||||
StatusNet::setApi(true);
|
||||
}
|
||||
|
||||
// Only allow POST requests
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
// TRANS: Client error displayed trying to perform any request method other than POST.
|
||||
// TRANS: Do not translate POST.
|
||||
$this->clientError(_('This action only accepts POST requests.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// CSRF protection
|
||||
|
||||
$token = $this->trimmed('token');
|
||||
|
||||
if (!$token || $token != common_session_token()) {
|
||||
// TRANS: Client error displayed when the session token is not okay.
|
||||
$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)) {
|
||||
// TRANS: Client error displayed trying to subscribe when not logged in.
|
||||
$this->clientError(_('Not logged in.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Profile to subscribe to
|
||||
|
||||
$this->tag = $this->arg('tag');
|
||||
|
||||
if (empty($this->tag)) {
|
||||
// TRANS: Client error displayed trying to subscribe to a non-existing profile.
|
||||
$this->clientError(_('No such profile.'));
|
||||
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
|
||||
|
||||
TagSub::start($this->user->getProfile(),
|
||||
$this->tag);
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->startHTML('text/xml;charset=utf-8');
|
||||
$this->elementStart('head');
|
||||
// TRANS: Page title when tag subscription succeeded.
|
||||
$this->element('title', null, _m('Subscribed'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$unsubscribe = new TagUnsubForm($this, $this->tag);
|
||||
$unsubscribe->show();
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
$url = common_local_url('tag',
|
||||
array('tag' => $this->tag));
|
||||
common_redirect($url, 303);
|
||||
}
|
||||
}
|
||||
}
|
142
plugins/TagSub/tagsubform.php
Normal file
142
plugins/TagSub/tagsubform.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Form for subscribing to a tag
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @copyright 2009-2011 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for subscribing to a user
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*
|
||||
* @see UnsubscribeForm
|
||||
*/
|
||||
|
||||
class TagSubForm extends Form
|
||||
{
|
||||
/**
|
||||
* Name of tag to subscribe to
|
||||
*/
|
||||
|
||||
var $tag = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param HTMLOutputter $out output channel
|
||||
* @param string $tag name of tag to subscribe to
|
||||
*/
|
||||
|
||||
function __construct($out=null, $tag=null)
|
||||
{
|
||||
parent::__construct($out);
|
||||
|
||||
$this->tag = $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* @return int ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'tag-subscribe-' . $this->tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class of the form
|
||||
*
|
||||
* @return string of the form class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
// class to match existing styles...
|
||||
return 'form_user_subscribe ajax';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action of the form
|
||||
*
|
||||
* @return string URL of the action
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('tagsub', array('tag' => $this->tag));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Legend of the Form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formLegend()
|
||||
{
|
||||
$this->out->element('legend', null, _m('Subscribe to this tag'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data elements of the form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formData()
|
||||
{
|
||||
$this->out->hidden('subscribeto-' . $this->tag,
|
||||
$this->tag,
|
||||
'subscribeto');
|
||||
}
|
||||
|
||||
/**
|
||||
* Action elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit('submit', _('Subscribe'), 'submit', null, _m('Subscribe to this tag'));
|
||||
}
|
||||
}
|
89
plugins/TagSub/tagunsubaction.php
Normal file
89
plugins/TagSub/tagunsubaction.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008-2011, StatusNet, Inc.
|
||||
*
|
||||
* Tag subscription action.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag unsubscription action
|
||||
*
|
||||
* Takes parameters:
|
||||
*
|
||||
* - 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 Evan Prodromou <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2008-2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
class TagunsubAction extends TagsubAction
|
||||
{
|
||||
/**
|
||||
* Handle request
|
||||
*
|
||||
* Does the subscription and returns results.
|
||||
*
|
||||
* @param Array $args unused.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
// Throws exception on error
|
||||
|
||||
TagSub::cancel($this->user->getProfile(),
|
||||
$this->tag);
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->startHTML('text/xml;charset=utf-8');
|
||||
$this->elementStart('head');
|
||||
// TRANS: Page title when tag unsubscription succeeded.
|
||||
$this->element('title', null, _m('Unsubscribed'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$subscribe = new TagSubForm($this, $this->tag);
|
||||
$subscribe->show();
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
$url = common_local_url('tag',
|
||||
array('tag' => $this->tag));
|
||||
common_redirect($url, 303);
|
||||
}
|
||||
}
|
||||
}
|
109
plugins/TagSub/tagunsubform.php
Normal file
109
plugins/TagSub/tagunsubform.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Form for subscribing to a tag
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @copyright 2009-2011 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for subscribing to a user
|
||||
*
|
||||
* @category TagSubPlugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://status.net/
|
||||
*
|
||||
* @see UnsubscribeForm
|
||||
*/
|
||||
|
||||
class TagUnsubForm extends TagSubForm
|
||||
{
|
||||
/**
|
||||
* ID of the form
|
||||
*
|
||||
* @return int ID of the form
|
||||
*/
|
||||
|
||||
function id()
|
||||
{
|
||||
return 'tag-unsubscribe-' . $this->tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class of the form
|
||||
*
|
||||
* @return string of the form class
|
||||
*/
|
||||
|
||||
function formClass()
|
||||
{
|
||||
// class to match existing styles...
|
||||
return 'form_user_unsubscribe ajax';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action of the form
|
||||
*
|
||||
* @return string URL of the action
|
||||
*/
|
||||
|
||||
function action()
|
||||
{
|
||||
return common_local_url('tagunsub', array('tag' => $this->tag));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Legend of the Form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function formLegend()
|
||||
{
|
||||
$this->out->element('legend', null, _m('Unsubscribe from this tag'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function formActions()
|
||||
{
|
||||
$this->out->submit('submit', _('Unsubscribe'), 'submit', null, _m('Unsubscribe from this tag'));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user