Merge branch '0.9.x' into 1.0.x

Conflicts:
	README
This commit is contained in:
Evan Prodromou 2011-01-12 18:05:56 -05:00
commit f9b2feb7f5
30 changed files with 1113 additions and 226 deletions

View File

@ -1063,3 +1063,15 @@ StartProfileSettingsActions: when we're showing account-management action list
EndProfileSettingsActions: when we're showing account-management action list
- $action: Action being shown (use for output)
StartOpenNoticeListItemElement: Before the opening <li> of a notice list element
- $nli: The notice list item being shown
EndOpenNoticeListItemElement: After the opening <li> of a notice list element
- $nli: The notice list item being shown
StartCloseNoticeListItemElement: Before the closing </li> of a notice list element
- $nli: The notice list item being shown
EndCloseNoticeListItemElement: After the closing </li> of a notice list element
- $nli: The notice list item being shown

15
README
View File

@ -1570,6 +1570,21 @@ cache: whether to cache the router in memcache (or another caching
router cached) or others who see strange behavior. You're unlikely
to need this unless you're a developer.
http
----
Settings for the HTTP client.
ssl_cafile: location of the CA file for SSL. If not set, won't verify
SSL peers. Default unset.
curl: Use cURL <http://curl.haxx.se/> for doing HTTP calls. You must
have the PHP curl extension installed for this to work.
proxy_host: Host to use for proxying HTTP requests. If unset, doesn't
do any HTTP proxy stuff. Default unset.
proxy_port: Port to use to connect to HTTP proxy host. Default null.
proxy_user: Username to use for authenticating to the HTTP proxy. Default null.
proxy_password: Password to use for authenticating to the HTTP proxy. Default null.
proxy_auth_scheme: Scheme to use for authenticating to the HTTP proxy. Default null.
Plugins
=======

View File

@ -107,7 +107,7 @@ class Design extends Memcached_DataObject
static function toWebColor($color)
{
if ($color == null) {
if ($color === null || $color === '') {
return null;
}

View File

@ -62,4 +62,5 @@ VALUES
(100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()),
(100115, 'E-Plus', '%s@smsmail.eplus.de', now()),
(100116, 'Cellular South', '%s@csouth1.com', now()),
(100117, 'ChinaMobile (139)', '%s@139.com', now());
(100117, 'ChinaMobile (139)', '%s@139.com', now()),
(100118, 'Dialog Axiata', '%s@dialog.lk', now());

View File

@ -219,7 +219,7 @@ function main()
{
// fake HTTP redirects using lighttpd's 404 redirects
if (strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) {
$_lighty_url = $base_url.$_SERVER['REQUEST_URI'];
$_lighty_url = $_SERVER['REQUEST_URI'];
$_lighty_url = @parse_url($_lighty_url);
if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') {

View File

@ -1,12 +1,15 @@
.fake: all clean
TARGETS=util.min.js
SOURCES=util.js xbImportNode.js geometa.js
TARGETS=util.min.js json2.min.js
UTIL_SOURCES=util.js xbImportNode.js geometa.js
all: $(TARGETS)
clean:
rm -f $(TARGETS)
util.min.js: $(SOURCES)
util.min.js: $(UTIL_SOURCES)
cat $+ | yui-compressor --type js > $@
json2.min.js: json2.js
yui-compressor $+ > $@

View File

@ -1,4 +1,3 @@
alert('IMPORTANT: Remove this line from json2.js before deployment.');
/*
http://www.JSON.org/json2.js
2010-08-25

2
js/json2.min.js vendored
View File

@ -1 +1 @@
alert("IMPORTANT: Remove this line from json2.js before deployment.");if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());
if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());

View File

@ -339,6 +339,11 @@ $default =
'http' => // HTTP client settings when contacting other sites
array('ssl_cafile' => false, // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt')
'curl' => false, // Use CURL backend for HTTP fetches if available. (If not, PHP's socket streams will be used.)
'proxy_host' => null,
'proxy_port' => null,
'proxy_user' => null,
'proxy_password' => null,
'proxy_auth_scheme' => null,
),
'router' =>
array('cache' => true), // whether to cache the router object. Defaults to true, turn off for devel

View File

@ -149,6 +149,14 @@ class HTTPClient extends HTTP_Request2
$this->config['adapter'] = 'HTTP_Request2_Adapter_Curl';
}
foreach (array('host', 'port', 'user', 'password', 'auth_scheme') as $cf) {
$k = 'proxy_'.$cf;
$v = common_config('http', $k);
if (!empty($v)) {
$this->config[$k] = $v;
}
}
parent::__construct($url, $method, $config);
$this->setHeader('User-Agent', $this->userAgent());
}

View File

@ -103,15 +103,17 @@ class Nickname
*/
public static function normalize($str)
{
if (mb_strlen($str) > self::MAX_LEN) {
// Display forms must also fit!
throw new NicknameTooLongException();
}
$str = trim($str);
$str = str_replace('_', '', $str);
$str = mb_strtolower($str);
$len = mb_strlen($str);
if ($len < 1) {
if (mb_strlen($str) < 1) {
throw new NicknameEmptyException();
} else if ($len > self::MAX_LEN) {
throw new NicknameTooLongException();
}
if (!self::isCanonical($str)) {
throw new NicknameInvalidException();

View File

@ -263,11 +263,12 @@ class NoticeListItem extends Widget
function showStart()
{
// XXX: RDFa
// TODO: add notice_type class e.g., notice_video, notice_image
if (Event::handle('StartOpenNoticeListItemElement', array($this))) {
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
$this->out->elementStart('li', array('class' => 'hentry notice',
'id' => 'notice-' . $id));
Event::handle('EndOpenNoticeListItemElement', array($this));
}
}
/**
@ -706,6 +707,9 @@ class NoticeListItem extends Widget
function showEnd()
{
if (Event::handle('StartCloseNoticeListItemElement', array($this))) {
$this->out->elementEnd('li');
Event::handle('EndCloseNoticeListItemElement', array($this));
}
}
}

109
lib/uuid.php Normal file
View File

@ -0,0 +1,109 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* UUID generation
*
* 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 UUID
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* UUID generation
*
* @category General
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class UUID
{
protected $str = null;
/**
* Constructor for a UUID
*
* Uses gen() to create a new UUID
*/
function __construct()
{
$this->str = self::gen();
}
/**
* For serializing to a string
*
* @return string version of self
*/
function __toString()
{
return $this->str;
}
/**
* For serializing to a string
*
* @return string version of self
*/
function getString()
{
return $this->str;
}
/**
* Generate a new UUID
*
* @return 36-char v4 (random-ish) UUID
*/
static function gen()
{
return sprintf('%s-%s-%04x-%04x-%s',
// 32 bits for "time_low"
common_good_rand(4),
// 16 bits for "time_mid"
common_good_rand(2),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
(hexdec(common_good_rand(2)) & 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one
// for variant DCE1.1
(hexdec(common_good_rand(2)) & 0x3fff) | 0x8000,
// 48 bits for "node"
common_good_rand(6));
}
}

View File

@ -8247,7 +8247,7 @@ msgstr ""
#: lib/mail.php:680
#, php-format
msgid "%1$s (@%2$s) sent a notice to your attention"
msgstr "%1$s (@%2$s) a envoyé un avis à vote attention"
msgstr "%1$s (@%2$s) a envoyé un avis à votre attention"
#. TRANS: Body of @-reply notification e-mail.
#. TRANS: %1$s is the sending user's long name, $2$s is the StatusNet sitename,

View File

@ -46,12 +46,12 @@ if (!defined('STATUSNET')) {
class Bookmark extends Memcached_DataObject
{
public $__table = 'bookmark'; // table name
public $profile_id; // int(4) primary_key not_null
public $url; // varchar(255) primary_key not_null
public $id; // char(36) primary_key not_null
public $profile_id; // int(4) not_null
public $url; // varchar(255) not_null
public $title; // varchar(255)
public $description; // text
public $uri; // varchar(255)
public $url_crc32; // int(4) not_null
public $created; // datetime
/**
@ -100,12 +100,12 @@ class Bookmark extends Memcached_DataObject
function table()
{
return array('profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
return array('id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'url' => DB_DATAOBJECT_STR,
'title' => DB_DATAOBJECT_STR,
'description' => DB_DATAOBJECT_STR,
'uri' => DB_DATAOBJECT_STR,
'url_crc32' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE +
DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
}
@ -129,8 +129,7 @@ class Bookmark extends Memcached_DataObject
function keyTypes()
{
return array('profile_id' => 'K',
'url' => 'K',
return array('id' => 'K',
'uri' => 'U');
}
@ -169,38 +168,18 @@ class Bookmark extends Memcached_DataObject
static function getByURL($profile, $url)
{
return self::pkeyGet(array('profile_id' => $profile->id,
'url' => $url));
return null;
}
/**
* Get the bookmark that a user made for an URL
*
* @param Profile $profile Profile to check for
* @param integer $crc32 CRC-32 of URL to check for
*
* @return array Bookmark objects found (usually 1 or 0)
*/
static function getByCRC32($profile, $crc32)
{
$bookmarks = array();
$nb = new Bookmark();
$nb->profile_id = $profile->id;
$nb->url_crc32 = $crc32;
$nb->url = $url;
if ($nb->find()) {
while ($nb->fetch()) {
$bookmarks[] = clone($nb);
if ($nb->find(true)) {
return $nb;
} else {
return null;
}
}
return $bookmarks;
}
/**
* Save a new notice bookmark
*
@ -240,11 +219,11 @@ class Bookmark extends Memcached_DataObject
$nb = new Bookmark();
$nb->id = UUID::gen();
$nb->profile_id = $profile->id;
$nb->url = $url;
$nb->title = $title;
$nb->description = $description;
$nb->url_crc32 = crc32($nb->url);
if (array_key_exists('created', $options)) {
$nb->created = $options['created'];
@ -255,22 +234,8 @@ class Bookmark extends Memcached_DataObject
if (array_key_exists('uri', $options)) {
$nb->uri = $options['uri'];
} else {
$dt = new DateTime($nb->created, new DateTimeZone('UTC'));
// I posit that it's sufficiently impossible
// for the same user to generate two CRC-32-clashing
// URLs in the same second that this is a safe unique identifier.
// If you find a real counterexample, contact me at acct:evan@status.net
// and I will publicly apologize for my hubris.
$created = $dt->format('YmdHis');
$crc32 = sprintf('%08x', $nb->url_crc32);
$nb->uri = common_local_url('showbookmark',
array('user' => $profile->id,
'created' => $created,
'crc32' => $crc32));
array('id' => $nb->id));
}
$nb->insert();

View File

@ -86,16 +86,21 @@ class BookmarkPlugin extends Plugin
// For storing user-submitted flags on profiles
$schema->ensureTable('bookmark',
array(new ColumnDef('profile_id',
array(new ColumnDef('id',
'char',
36,
false,
'PRI'),
new ColumnDef('profile_id',
'integer',
null,
false,
'PRI'),
'MUL'),
new ColumnDef('url',
'varchar',
255,
false,
'PRI'),
'MUL'),
new ColumnDef('title',
'varchar',
255),
@ -106,26 +111,12 @@ class BookmarkPlugin extends Plugin
255,
false,
'UNI'),
new ColumnDef('url_crc32',
'integer unsigned',
null,
false,
'MUL'),
new ColumnDef('created',
'datetime',
null,
false,
'MUL')));
try {
$schema->createIndex('bookmark',
array('profile_id',
'url_crc32'),
'bookmark_profile_url_idx');
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
}
return true;
}
@ -216,11 +207,9 @@ class BookmarkPlugin extends Plugin
$m->connect('main/bookmark/import',
array('action' => 'importdelicious'));
$m->connect('bookmark/:user/:created/:crc32',
$m->connect('bookmark/:id',
array('action' => 'showbookmark'),
array('user' => '[0-9]+',
'created' => '[0-9]{14}',
'crc32' => '[0-9a-f]{8}'));
array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
$m->connect('notice/by-url/:id',
array('action' => 'noticebyurl'),
@ -262,25 +251,28 @@ class BookmarkPlugin extends Plugin
} else {
$out->elementStart('h3');
$out->element('a',
array('href' => $att->url),
array('href' => $att->url,
'class' => 'bookmark-title entry-title'),
$nb->title);
$out->elementEnd('h3');
$countUrl = common_local_url('noticebyurl',
array('id' => $att->id));
$out->element('a', array('class' => 'bookmark_notice_count',
$out->element('a', array('class' => 'bookmark-notice-count',
'href' => $countUrl),
$att->noticeCount());
}
$out->elementStart('ul', array('class' => 'bookmark_tags'));
// Replies look like "for:" tags
$replies = $nli->notice->getReplies();
$tags = $nli->notice->getTags();
if (!empty($replies) || !empty($tags)) {
$out->elementStart('ul', array('class' => 'bookmark-tags'));
if (!empty($replies)) {
foreach ($replies as $reply) {
$other = Profile::staticGet('id', $reply);
$out->elementStart('li');
@ -291,9 +283,6 @@ class BookmarkPlugin extends Plugin
$out->elementEnd('li');
$out->text(' ');
}
}
$tags = $nli->notice->getTags();
foreach ($tags as $tag) {
$out->elementStart('li');
@ -306,29 +295,46 @@ class BookmarkPlugin extends Plugin
}
$out->elementEnd('ul');
}
if (!empty($nb->description)) {
$out->element('p',
array('class' => 'bookmark_description'),
array('class' => 'bookmark-description'),
$nb->description);
}
if (common_config('attachments', 'show_thumbs')) {
$haveThumbs = false;
foreach ($atts as $check) {
$thumbnail = File_thumbnail::staticGet('file_id', $check->id);
if (!empty($thumbnail)) {
$haveThumbs = true;
break;
}
}
if ($haveThumbs) {
$al = new InlineAttachmentList($notice, $out);
$al->show();
}
}
$out->elementStart('p', array('style' => 'float: left'));
$out->elementStart('div', array('class' => 'bookmark-info entry-content'));
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
$out->element('img', array('src' => ($avatar) ?
$out->element('img',
array('src' => ($avatar) ?
$avatar->displayUrl() :
Avatar::defaultImage(AVATAR_MINI_SIZE),
'class' => 'avatar photo bookmark_avatar',
'class' => 'avatar photo bookmark-avatar',
'width' => AVATAR_MINI_SIZE,
'height' => AVATAR_MINI_SIZE,
'alt' => $profile->getBestName()));
$out->raw('&nbsp;');
$out->element('a', array('href' => $profile->profileurl,
$out->element('a',
array('href' => $profile->profileurl,
'title' => $profile->getBestName()),
$profile->nickname);
@ -338,7 +344,7 @@ class BookmarkPlugin extends Plugin
$nli->showContext();
$nli->showRepeat();
$out->elementEnd('p');
$out->elementEnd('div');
$nli->showNoticeOptions();
@ -642,6 +648,27 @@ class BookmarkPlugin extends Plugin
return true;
}
/**
* Output our CSS class for bookmark notice list elements
*
* @param NoticeListItem $nli The item being shown
*
* @return boolean hook value
*/
function onStartOpenNoticeListItemElement($nli)
{
$nb = Bookmark::getByNotice($nli->notice);
if (!empty($nb)) {
$id = (empty($nli->repeat)) ? $nli->notice->id : $nli->repeat->id;
$nli->out->elementStart('li', array('class' => 'hentry notice bookmark',
'id' => 'notice-' . $id));
Event::handle('EndOpenNoticeListItemElement', array($nli));
return false;
}
return true;
}
/**
* Save a remote bookmark (from Salmon or PuSH)
*
@ -769,4 +796,3 @@ class BookmarkPlugin extends Plugin
$activity->objects[0]->type == ActivityObject::BOOKMARK);
}
}

View File

@ -1,4 +1,6 @@
.bookmark_tags li { display: inline; }
.bookmark_mentions li { display: inline; }
.bookmark_avatar { float: left }
.bookmark_notice_count { float: right }
.bookmark-tags li { display: inline; }
.bookmark-mentions li { display: inline; }
.bookmark-avatar { float: left; }
.bookmark-notice-count { float: right; }
.bookmark-info { float: left; }
.bookmark-title { margin-left: 0px }

View File

@ -2,6 +2,13 @@ $(document).ready(
function() {
var form = $('#form_new_bookmark');
form.append('<input type="hidden" name="ajax" value="1"/>');
function doClose() {
self.close();
// If in popup blocker situation, we'll have to redirect back.
setTimeout(function() {
window.location = $('#url').val();
}, 100);
}
form.ajaxForm({dataType: 'xml',
timeout: '60000',
beforeSend: function(formData) {
@ -11,12 +18,12 @@ $(document).ready(
error: function (xhr, textStatus, errorThrown) {
form.removeClass('processing');
form.find('#submit').removeClass('disabled');
self.close();
doClose();
},
success: function(data, textStatus) {
form.removeClass('processing');
form.find('#submit').removeClass('disabled');
self.close();
doClose();
}});
}

View File

@ -65,7 +65,7 @@ class DeliciousBackupImporter extends QueueHandler
* and import to StatusNet as Bookmark activities.
*
* The document format is terrible. It consists of a <dl> with
* a bunch of <dt>'s, occasionally with <dd>'s.
* a bunch of <dt>'s, occasionally with <dd>'s adding descriptions.
* There are sometimes <p>'s lost inside.
*
* @param array $data pair of user, text
@ -99,6 +99,9 @@ class DeliciousBackupImporter extends QueueHandler
}
switch (strtolower($child->tagName)) {
case 'dt':
// <dt> nodes contain primary information about a bookmark.
// We can't import the current one just yet though, since
// it may be followed by a <dd>.
if (!empty($dt)) {
// No DD provided
$this->importBookmark($user, $dt);
@ -109,10 +112,13 @@ class DeliciousBackupImporter extends QueueHandler
case 'dd':
$dd = $child;
// This <dd> contains a description for the bookmark in
// the preceding <dt> node.
$saved = $this->importBookmark($user, $dt, $dd);
$dt = null;
$dd = null;
break;
case 'p':
common_log(LOG_INFO, 'Skipping the <p> in the <dl>.');
break;
@ -126,6 +132,14 @@ class DeliciousBackupImporter extends QueueHandler
$dt = $dd = null;
}
}
if (!empty($dt)) {
// There was a final bookmark without a description.
try {
$this->importBookmark($user, $dt);
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
}
}
return true;
}
@ -148,24 +162,38 @@ class DeliciousBackupImporter extends QueueHandler
function importBookmark($user, $dt, $dd = null)
{
// We have to go squirrelling around in the child nodes
// on the off chance that we've received another <dt>
// as a child.
$as = $dt->getElementsByTagName('a');
for ($i = 0; $i < $dt->childNodes->length; $i++) {
$child = $dt->childNodes->item($i);
if ($child->nodeType == XML_ELEMENT_NODE) {
if ($child->tagName == 'dt' && !is_null($dd)) {
$this->importBookmark($user, $dt);
$this->importBookmark($user, $child, $dd);
return;
if ($as->length == 0) {
throw new ClientException(_("No <A> tag in a <DT>."));
}
$a = $as->item(0);
$private = $a->getAttribute('private');
if ($private != 0) {
throw new ClientException(_('Skipping private bookmark.'));
}
if (!empty($dd)) {
$description = $dd->nodeValue;
} else {
$description = null;
}
$addDate = $a->getAttribute('add_date');
$data = array(
'profile_id' => $user->id,
'title' => $a->nodeValue,
'description' => $description,
'url' => $a->getAttribute('href'),
'tags' => $a->getAttribute('tags'),
'created' => common_sql_date(intval($addDate))
);
$qm = QueueManager::get();
$qm->enqueue(array($user, $dt, $dd), 'dlcsbkmk');
$qm->enqueue($data, 'dlcsbkmk');
}
/**
@ -188,9 +216,95 @@ class DeliciousBackupImporter extends QueueHandler
error_reporting($old);
if ($ok) {
foreach ($dom->getElementsByTagName('body') as $node) {
$this->fixListsIn($node);
}
return $dom;
} else {
return null;
}
}
function fixListsIn(DOMNode $body) {
$toFix = array();
foreach ($body->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$el = strtolower($node->nodeName);
if ($el == 'dl') {
$toFix[] = $node;
}
}
}
foreach ($toFix as $node) {
$this->fixList($node);
}
}
function fixList(DOMNode $list) {
$toFix = array();
foreach ($list->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$el = strtolower($node->nodeName);
if ($el == 'dt' || $el == 'dd') {
$toFix[] = $node;
}
if ($el == 'dl') {
// Sublist.
// Technically, these can only appear inside a <dd>...
$this->fixList($node);
}
}
}
foreach ($toFix as $node) {
$this->fixListItem($node);
}
}
function fixListItem(DOMNode $item) {
// The HTML parser in libxml2 doesn't seem to properly handle
// many cases of implied close tags, apparently because it doesn't
// understand the nesting rules specified in the HTML DTD.
//
// This leads to sequences of adjacent <dt>s or <dd>s being incorrectly
// interpreted as parent->child trees instead of siblings:
//
// When parsing this input: "<dt>aaa <dt>bbb"
// should be equivalent to: "<dt>aaa </dt><dt>bbb</dt>"
// but we're seeing instead: "<dt>aaa <dt>bbb</dt></dt>"
//
// It does at least know that going from dt to dd, or dd to dt,
// should make a break.
$toMove = array();
foreach ($item->childNodes as $node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
$el = strtolower($node->nodeName);
if ($el == 'dt' || $el == 'dd') {
// dt & dd cannot contain each other;
// This node was incorrectly placed; move it up a level!
$toMove[] = $node;
}
if ($el == 'dl') {
// Sublist.
// Technically, these can only appear inside a <dd>.
$this->fixList($node);
}
}
}
$parent = $item->parentNode;
$next = $item->nextSibling;
foreach ($toMove as $node) {
$item->removeChild($node);
$parent->insertBefore($node, $next);
$this->fixListItem($node);
}
}
}

View File

@ -61,48 +61,28 @@ class DeliciousBookmarkImporter extends QueueHandler
/**
* Handle the data
*
* @param array $data array of user, dt, dd
* @param array $data associative array of user & bookmark info from DeliciousBackupImporter::importBookmark()
*
* @return boolean success value
*/
function handle($data)
{
list($user, $dt, $dd) = $data;
$profile = Profile::staticGet('id', $data['profile_id']);
$as = $dt->getElementsByTagName('a');
if ($as->length == 0) {
throw new ClientException(_("No <A> tag in a <DT>."));
}
$a = $as->item(0);
$private = $a->getAttribute('private');
if ($private != 0) {
throw new ClientException(_('Skipping private bookmark.'));
}
if (!empty($dd)) {
$description = $dd->nodeValue;
} else {
$description = null;
}
$title = $a->nodeValue;
$url = $a->getAttribute('href');
$tags = $a->getAttribute('tags');
$addDate = $a->getAttribute('add_date');
$created = common_sql_date(intval($addDate));
$saved = Bookmark::saveNew($user->getProfile(),
$title,
$url,
$tags,
$description,
array('created' => $created,
try {
$saved = Bookmark::saveNew($profile,
$data['title'],
$data['url'],
$data['tags'],
$data['description'],
array('created' => $data['created'],
'distribute' => false));
} catch (ClientException $e) {
// Most likely a duplicate -- continue on with the rest!
common_log(LOG_ERR, "Error importing delicious bookmark to $data[url]: " . $e->getMessage());
return true;
}
return true;
}

View File

@ -48,6 +48,7 @@ if (!defined('STATUSNET')) {
class ImportdeliciousAction extends Action
{
protected $success = false;
private $inprogress = false;
/**
* Return the title of the page
@ -191,7 +192,13 @@ class ImportdeliciousAction extends Action
$qm = QueueManager::get();
$qm->enqueue(array(common_current_user(), $html), 'dlcsback');
if ($qm instanceof UnQueueManager) {
// No active queuing means we've actually just completed the job!
$this->success = true;
} else {
// We've fed data into background queues, and it's probably still running.
$this->inprogress = true;
}
$this->showPage();
@ -212,8 +219,10 @@ class ImportdeliciousAction extends Action
{
if ($this->success) {
$this->element('p', null,
_('Feed will be restored. '.
'Please wait a few minutes for results.'));
_('Bookmarks have been imported. Your bookmarks should now appear in search and your profile page.'));
} else if ($this->inprogress) {
$this->element('p', null,
_('Bookmarks are being imported. Please wait a few minutes for results.'));
} else {
$form = new ImportDeliciousForm($this);
$form->show();

View File

@ -61,7 +61,22 @@ class ShowbookmarkAction extends ShownoticeAction
{
OwnerDesignAction::prepare($argarray);
$this->user = User::staticGet('id', $this->trimmed('user'));
$this->id = $this->trimmed('id');
$this->bookmark = Bookmark::staticGet('id', $this->id);
if (empty($this->bookmark)) {
throw new ClientException(_('No such bookmark.'), 404);
}
$this->notice = Notice::staticGet('uri', $this->bookmark->uri);
if (empty($this->notice)) {
// Did we used to have it, and it got deleted?
throw new ClientException(_('No such bookmark.'), 404);
}
$this->user = User::staticGet('id', $this->bookmark->profile_id);
if (empty($this->user)) {
throw new ClientException(_('No such user.'), 404);
@ -75,41 +90,6 @@ class ShowbookmarkAction extends ShownoticeAction
$this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
sscanf($this->trimmed('crc32'), '%08x', $crc32);
if (empty($crc32)) {
throw new ClientException(_('No such URL.'), 404);
}
$dt = new DateTime($this->trimmed('created'),
new DateTimeZone('UTC'));
if (empty($dt)) {
throw new ClientException(_('No such create date.'), 404);
}
$bookmarks = Bookmark::getByCRC32($this->profile,
$crc32);
foreach ($bookmarks as $bookmark) {
$bdt = new DateTime($bookmark->created, new DateTimeZone('UTC'));
if ($bdt->format('U') == $dt->format('U')) {
$this->bookmark = $bookmark;
break;
}
}
if (empty($this->bookmark)) {
throw new ClientException(_('No such bookmark.'), 404);
}
$this->notice = Notice::staticGet('uri', $this->bookmark->uri);
if (empty($this->notice)) {
// Did we used to have it, and it got deleted?
throw new ClientException(_('No such bookmark.'), 404);
}
return true;
}

View File

@ -1,3 +1,9 @@
*** WARNING ***
This plugin is deprecated as of StatusNet 0.9.7, and will soon be removed
completely from the StatusNet codebase. Please install or upgrade to the
new "Facebook Bridge" (plugins/FacebookBridge) plugin ASAP.
***************
Facebook Plugin
===============

View File

@ -0,0 +1,108 @@
Facebook Bridge Plugin
The Facebook Bridge plugin allows you to integrate your StatusNet site
with Facebook. It uses Facebook's new SDKs, the Graph API and OAuth
2.0, and supercedes the previous "Facebook" plugin, which relied on the
Facebook's now deprecated "Old REST API". The other major difference is
the troublesome and confusing Facebook Canvas Application has been
removed.
Note: Do NOT try to run the old Facebook plugin and this plugin at the same
time. It won't work.
Features for the new Facebook Bridge Plugin:
- "Login with Facebook" (AKA single-sign-on using Facebook Connect for
authentication).
- Registration with Facebook Connect, including automatic profile creation
based on users' Facebook accounts.
- Post mirroring -- posting a notice on StatusNet automatically creates a post
on Facebook, deleting it on StatusNet deletes it on Facebook, and faving it
"likes" it on Facebook.
Upgrading from the old Facebook plugin
======================================
Remove the addPlugin('Facebook') statement for the old Facebook plugin
from your config.php and adjust your existing Facebook application using
the setup instructions below as a guide, then enable the new plugin (also
described below). Existing users who have setup post mirroring shouldn't
have to do anything. The new Facebook Bridge plugin will keep their notices
flowing to Facebook.
Setup
=====
There are two parts configuring the Facebook Bridge plugin -- setup on the
Facebook side, and setup on the StatusNet side.
Setup (Facebook side)
=====================
The first step is to login to Facebook and register a Facebook application
to get an application ID and secret.
Use the handy Facebook application setup wizard:
http://developers.facebook.com/setup/
Once you've set up your application, you'll need to enter the Facebook Developer
dashboard (http://www.facebook.com/developers/editapp.php?app_id=YOUR_APPLICATION_ID)
and customize a few things:
About tab
---------
Set your logos, application description, etc. as you see fit.
Web Site tab
------------
Make a note of the "Application ID" and "Application Secret" Facebook generated
for your application. You'll need both of those later.
Make sure "Site URL" points to your StatusNet installation
(e.g.: http://example.net/).
- Special Note for multi-site Status Networks: enter your domain (SLD and TLD)
in the "Site Domain" field. (e.g.: status.net). This will allow a single
Facebook Application to work with all your network subdomains (*.status.net).
Facebook Integration tab
------------------------
Use the default settings.
Mobile and Devices tab
----------------------
Use the default settings.
Advanced tab
------------
In the Authentication section, set the "Deauthorize Callback" to
http://YOURSITE/facebook/deauthorize (e.g.: http://example.net/facebook/deauthorize).
In the Migrations section, ensure that "OAuth 2.0 for Canvas" is set to
"Enabled". It probably already will be, but double check.
The default settings should suffice for everything else.
Setup (StatusNet side)
======================
To enable the Facebook Bridge plugin, add
addPlugin('FacebookBridge');
to you config.php.
Next login to your StatusNet site as a user with the administrator role, and
navigate to the admin menu. You should see a new tab for the Facebook admin
panel, titled "Facebook". Enter your Facebook application ID and secret in
that admin panel and hit save. After that, the Facebook Bridge Plugin should
be ready to use.

View File

@ -299,6 +299,10 @@ class FacebookfinishloginAction extends Action
function createNewUser()
{
if (!Event::handle('StartRegistrationTry', array($this))) {
return;
}
if (common_config('site', 'closed')) {
// TRANS: Client error trying to register with registrations not allowed.
$this->clientError(_m('Registration not allowed.'));
@ -389,6 +393,8 @@ class FacebookfinishloginAction extends Action
__FILE__
);
Event::handle('EndRegistrationTry', array($this));
$this->goHome($user->nickname);
}

View File

@ -0,0 +1,435 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* Do a different menu layout
*
* 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 Sample
* @package StatusNet
* @author Brion Vibber <brionv@status.net>
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Somewhat different menu navigation
*
* We have a new menu layout coming in StatusNet 1.0. This plugin gets
* some of the new navigation in, although third-level menus aren't enabled.
*
* @category NewMenu
* @package StatusNet
* @author Brion Vibber <brionv@status.net>
* @author Evan Prodromou <evan@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class NewMenuPlugin extends Plugin
{
public $loadCSS = false;
/**
* 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 'HelloAction':
include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'User_greeting_count':
include_once $dir . '/'.$cls.'.php';
return false;
default:
return true;
}
}
/**
* Modify the default menu
*
* @param Action $action The current action handler. Use this to
* do any output.
*
* @return boolean hook value; true means continue processing, false means stop.
*
* @see Action
*/
function onStartPrimaryNav($action)
{
$user = common_current_user();
if (!empty($user)) {
$action->menuItem(common_local_url('all',
array('nickname' => $user->nickname)),
_m('Home'),
_m('Friends timeline'),
false,
'nav_home');
$action->menuItem(common_local_url('showstream',
array('nickname' => $user->nickname)),
_m('Profile'),
_m('Your profile'),
false,
'nav_profile');
$action->menuItem(common_local_url('public'),
_m('Public'),
_m('Everyone on this site'),
false,
'nav_public');
$action->menuItem(common_local_url('profilesettings'),
_m('Settings'),
_m('Change your personal settings'),
false,
'nav_account');
if ($user->hasRight(Right::CONFIGURESITE)) {
$action->menuItem(common_local_url('siteadminpanel'),
_m('Admin'),
_m('Site configuration'),
false,
'nav_admin');
}
$action->menuItem(common_local_url('logout'),
_m('Logout'),
_m('Logout from the site'),
false,
'nav_logout');
} else {
$action->menuItem(common_local_url('public'),
_m('Public'),
_m('Everyone on this site'),
false,
'nav_public');
$action->menuItem(common_local_url('login'),
_m('Login'),
_m('Login to the site'),
false,
'nav_login');
}
if (!empty($user) || !common_config('site', 'private')) {
$action->menuItem(common_local_url('noticesearch'),
_m('Search'),
_m('Search the site'),
false,
'nav_search');
}
Event::handle('EndPrimaryNav', array($action));
return false;
}
function onStartPersonalGroupNav($menu)
{
$user = null;
// FIXME: we should probably pass this in
$action = $menu->action->trimmed('action');
$nickname = $menu->action->trimmed('nickname');
if ($nickname) {
$user = User::staticGet('nickname', $nickname);
$user_profile = $user->getProfile();
$name = $user_profile->getBestName();
} else {
// @fixme can this happen? is this valid?
$user_profile = false;
$name = $nickname;
}
$menu->out->menuItem(common_local_url('all', array('nickname' =>
$nickname)),
_('Home'),
sprintf(_('%s and friends'), $name),
$action == 'all', 'nav_timeline_personal');
$menu->out->menuItem(common_local_url('replies', array('nickname' =>
$nickname)),
_('Replies'),
sprintf(_('Replies to %s'), $name),
$action == 'replies', 'nav_timeline_replies');
$menu->out->menuItem(common_local_url('showfavorites', array('nickname' =>
$nickname)),
_('Favorites'),
sprintf(_('%s\'s favorite notices'), ($user_profile) ? $name : _('User')),
$action == 'showfavorites', 'nav_timeline_favorites');
$cur = common_current_user();
if ($cur && $cur->id == $user->id &&
!common_config('singleuser', 'enabled')) {
$menu->out->menuItem(common_local_url('inbox', array('nickname' =>
$nickname)),
_('Inbox'),
_('Your incoming messages'),
$action == 'inbox');
$menu->out->menuItem(common_local_url('outbox', array('nickname' =>
$nickname)),
_('Outbox'),
_('Your sent messages'),
$action == 'outbox');
}
Event::handle('EndPersonalGroupNav', array($menu));
return false;
}
function onStartSubGroupNav($menu)
{
$cur = common_current_user();
$action = $menu->action->trimmed('action');
$profile = $menu->user->getProfile();
$menu->out->menuItem(common_local_url('showstream', array('nickname' =>
$menu->user->nickname)),
_('Profile'),
(empty($profile)) ? $menu->user->nickname : $profile->getBestName(),
$action == 'showstream',
'nav_profile');
$menu->out->menuItem(common_local_url('subscriptions',
array('nickname' =>
$menu->user->nickname)),
_('Subscriptions'),
sprintf(_('People %s subscribes to'),
$menu->user->nickname),
$action == 'subscriptions',
'nav_subscriptions');
$menu->out->menuItem(common_local_url('subscribers',
array('nickname' =>
$menu->user->nickname)),
_('Subscribers'),
sprintf(_('People subscribed to %s'),
$menu->user->nickname),
$action == 'subscribers',
'nav_subscribers');
$menu->out->menuItem(common_local_url('usergroups',
array('nickname' =>
$menu->user->nickname)),
_('Groups'),
sprintf(_('Groups %s is a member of'),
$menu->user->nickname),
$action == 'usergroups',
'nav_usergroups');
if (common_config('invite', 'enabled') && !is_null($cur) && $menu->user->id === $cur->id) {
$menu->out->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
$action == 'invite',
'nav_invite');
}
Event::handle('EndSubGroupNav', array($menu));
return false;
}
function onStartShowLocalNavBlock($action)
{
$actionName = $action->trimmed('action');
if ($actionName == 'showstream') {
$action->elementStart('dl', array('id' => 'site_nav_local_views'));
// TRANS: DT element for local views block. String is hidden in default CSS.
$action->element('dt', null, _('Local views'));
$action->elementStart('dd');
$nav = new SubGroupNav($action, $action->user);
$nav->show();
$action->elementEnd('dd');
$action->elementEnd('dl');
Event::handle('EndShowLocalNavBlock', array($action));
return false;
}
return true;
}
function onStartAccountSettingsNav(&$action)
{
$this->_settingsMenu($action);
return false;
}
function onStartConnectSettingsNav(&$action)
{
$this->_settingsMenu($action);
return false;
}
private function _settingsMenu(&$action)
{
$actionName = $action->trimmed('action');
$action->menuItem(common_local_url('profilesettings'),
_('Profile'),
_('Change your profile settings'),
$actionName == 'profilesettings');
$action->menuItem(common_local_url('avatarsettings'),
_('Avatar'),
_('Upload an avatar'),
$actionName == 'avatarsettings');
$action->menuItem(common_local_url('passwordsettings'),
_('Password'),
_('Change your password'),
$actionName == 'passwordsettings');
$action->menuItem(common_local_url('emailsettings'),
_('Email'),
_('Change email handling'),
$actionName == 'emailsettings');
$action->menuItem(common_local_url('userdesignsettings'),
_('Design'),
_('Design your profile'),
$actionName == 'userdesignsettings');
$action->menuItem(common_local_url('othersettings'),
_('Other'),
_('Other options'),
$actionName == 'othersettings');
Event::handle('EndAccountSettingsNav', array(&$action));
if (common_config('xmpp', 'enabled')) {
$action->menuItem(common_local_url('imsettings'),
_m('IM'),
_('Updates by instant messenger (IM)'),
$actionName == 'imsettings');
}
if (common_config('sms', 'enabled')) {
$action->menuItem(common_local_url('smssettings'),
_m('SMS'),
_('Updates by SMS'),
$actionName == 'smssettings');
}
$action->menuItem(common_local_url('oauthconnectionssettings'),
_('Connections'),
_('Authorized connected applications'),
$actionName == 'oauthconnectionsettings');
Event::handle('EndConnectSettingsNav', array(&$action));
}
function onEndShowStyles($action)
{
if (($this->showCSS ||
in_array(common_config('site', 'theme'),
array('default', 'identica', 'h4ck3r'))) &&
($action instanceof AccountSettingsAction ||
$action instanceof ConnectSettingsAction)) {
$action->cssLink(common_path('plugins/NewMenu/newmenu.css'));
}
return true;
}
function onStartAddressData($action)
{
if (common_config('singleuser', 'enabled')) {
$user = User::singleUser();
$url = common_local_url('showstream',
array('nickname' => $user->nickname));
} else if (common_logged_in()) {
$cur = common_current_user();
$url = common_local_url('all', array('nickname' => $cur->nickname));
} else {
$url = common_local_url('public');
}
$action->elementStart('a', array('class' => 'url home bookmark',
'href' => $url));
if (StatusNet::isHTTPS()) {
$logoUrl = common_config('site', 'ssllogo');
if (empty($logoUrl)) {
// if logo is an uploaded file, try to fall back to HTTPS file URL
$httpUrl = common_config('site', 'logo');
if (!empty($httpUrl)) {
$f = File::staticGet('url', $httpUrl);
if (!empty($f) && !empty($f->filename)) {
// this will handle the HTTPS case
$logoUrl = File::url($f->filename);
}
}
}
} else {
$logoUrl = common_config('site', 'logo');
}
if (empty($logoUrl) && file_exists(Theme::file('logo.png'))) {
// This should handle the HTTPS case internally
$logoUrl = Theme::path('logo.png');
}
if (!empty($logoUrl)) {
$action->element('img', array('class' => 'logo photo',
'src' => $logoUrl,
'alt' => common_config('site', 'name')));
}
$action->text(' ');
$action->element('span', array('class' => 'fn org'), common_config('site', 'name'));
$action->elementEnd('a');
Event::handle('EndAddressData', array($action));
return false;
}
/**
* Return version information for this plugin
*
* @param array &$versions Version info; add to this array
*
* @return boolean hook value
*/
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'NewMenu',
'version' => STATUSNET_VERSION,
'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:NewMenu',
'description' =>
_m('A preview of the new menu '.
'layout in StatusNet 1.0.'));
return true;
}
}

View File

@ -0,0 +1,47 @@
body[id$=settings] #site_nav_local_views {
position:relative;
z-index:9;
float:right;
margin-right:10.65%;
width:22.25%;
}
body[id$=settings] #site_nav_local_views li {
width:100%;
margin-right:0;
margin-bottom:7px;
}
body[id$=settings] #site_nav_local_views a {
display:block;
width:80%;
padding-right:10%;
padding-left:10%;
border-radius-toprleft:0;
-moz-border-radius-topleft:0;
-webkit-border-top-left-radius:0;
border-radius-topright:4px;
-moz-border-radius-topright:4px;
-webkit-border-top-right-radius:4px;
border-radius-bottomright:4px;
-moz-border-radius-bottomright:4px;
-webkit-border-bottom-right-radius:4px;
}
body[id$=settings] #site_nav_local_views li.current {
box-shadow:none;
-moz-box-shadow:none;
-webkit-box-shadow:none;
}
body[id$=settings] #content {
border-radius-topleft:7px;
border-radius-topright:7px;
-moz-border-radius-topleft:7px;
-moz-border-radius-topright:7px;
-webkit-border-top-left-radius:7px;
-webkit-border-top-right-radius:7px;
border-radius-topright:0;
-moz-border-radius-topright:0;
-webkit-border-top-right-radius:0;
}
body[id$=settings] #aside_primary {
display:none;
}

View File

@ -194,7 +194,7 @@ class ActivityGenerationTests extends PHPUnit_Framework_TestCase
$actor = ActivityUtils::child($element, 'actor', Activity::SPEC);
$this->assertFalse(is_null($author));
$this->assertFalse(is_null($actor));
$this->assertTrue(is_null($actor)); // <activity:actor> is obsolete, no longer added
}
public function testAuthorContent()
@ -213,6 +213,9 @@ class ActivityGenerationTests extends PHPUnit_Framework_TestCase
$this->assertEquals($this->author1->uri, ActivityUtils::childContent($author, 'uri'));
}
/**
* We no longer create <activity:actor> entries, they have merged to <atom:author>
*/
public function testActorContent()
{
$notice = $this->_fakeNotice();
@ -225,8 +228,7 @@ class ActivityGenerationTests extends PHPUnit_Framework_TestCase
$actor = ActivityUtils::child($element, 'actor', Activity::SPEC);
$this->assertEquals($this->author1->uri, ActivityUtils::childContent($actor, 'id'));
$this->assertEquals($this->author1->nickname, ActivityUtils::childContent($actor, 'title'));
$this->assertEquals($actor, null);
}
public function testReplyLink()

View File

@ -33,9 +33,14 @@ class NicknameTest extends PHPUnit_Framework_TestCase
if ($expected === false) {
if ($expectedException) {
if ($exception) {
$stuff = get_class($exception) . ': ' . $exception->getMessage();
} else {
$stuff = var_export($exception, true);
}
$this->assertTrue($exception && $exception instanceof $expectedException,
"invalid input '$input' expected to fail with $expectedException, " .
"got " . get_class($exception) . ': ' . $exception->getMessage());
"got $stuff");
} else {
$this->assertTrue($normalized == false,
"invalid input '$input' expected to fail");
@ -104,7 +109,7 @@ class NicknameTest extends PHPUnit_Framework_TestCase
array('', false, 'NicknameEmptyException'),
array('___', false, 'NicknameEmptyException'),
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'), // 64 chars
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee_', 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'), // the _ will be trimmed off, remaining valid
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee_', false, 'NicknameTooLongException'), // the _ is too long...
array('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', false, 'NicknameTooLongException'), // 65 chars -- too long
);
}

37
tests/UUIDTest.php Normal file
View File

@ -0,0 +1,37 @@
<?php
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit();
}
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('STATUSNET', true);
require_once INSTALLDIR . '/lib/common.php';
class UUIDTest extends PHPUnit_Framework_TestCase
{
public function testGenerate()
{
$result = UUID::gen();
$this->assertRegExp('/^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/',
$result);
// Check version number
$this->assertEquals(0x4000, hexdec(substr($result, 14, 4)) & 0xF000);
$this->assertEquals(0x8000, hexdec(substr($result, 19, 4)) & 0xC000);
}
public function testUnique()
{
$reps = 100;
$ids = array();
for ($i = 0; $i < $reps; $i++) {
$ids[] = UUID::gen();
}
$this->assertEquals(count($ids), count(array_unique($ids)), "UUIDs must be unique");
}
}