Merge branch '0.9.x' into 1.0.x
Conflicts: README
This commit is contained in:
commit
f9b2feb7f5
12
EVENTS.txt
12
EVENTS.txt
@ -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
15
README
@ -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
|
||||
=======
|
||||
|
@ -107,7 +107,7 @@ class Design extends Memcached_DataObject
|
||||
|
||||
static function toWebColor($color)
|
||||
{
|
||||
if ($color == null) {
|
||||
if ($color === null || $color === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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'] != '/') {
|
||||
|
@ -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 $+ > $@
|
||||
|
@ -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
2
js/json2.min.js
vendored
@ -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")}}}());
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -263,11 +263,12 @@ class NoticeListItem extends Widget
|
||||
|
||||
function showStart()
|
||||
{
|
||||
// XXX: RDFa
|
||||
// TODO: add notice_type class e.g., notice_video, notice_image
|
||||
$id = (empty($this->repeat)) ? $this->notice->id : $this->repeat->id;
|
||||
$this->out->elementStart('li', array('class' => 'hentry notice',
|
||||
'id' => 'notice-' . $id));
|
||||
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()
|
||||
{
|
||||
$this->out->elementEnd('li');
|
||||
if (Event::handle('StartCloseNoticeListItemElement', array($this))) {
|
||||
$this->out->elementEnd('li');
|
||||
Event::handle('EndCloseNoticeListItemElement', array($this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
109
lib/uuid.php
Normal file
109
lib/uuid.php
Normal 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));
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -46,13 +46,13 @@ 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 $title; // varchar(255)
|
||||
public $description; // text
|
||||
public $uri; // varchar(255)
|
||||
public $url_crc32; // int(4) not_null
|
||||
public $created; // datetime
|
||||
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 $created; // datetime
|
||||
|
||||
/**
|
||||
* Get an instance by key
|
||||
@ -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,36 +168,16 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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();
|
||||
|
@ -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)) {
|
||||
if (!empty($replies) || !empty($tags)) {
|
||||
|
||||
$out->elementStart('ul', array('class' => 'bookmark-tags'));
|
||||
|
||||
foreach ($replies as $reply) {
|
||||
$other = Profile::staticGet('id', $reply);
|
||||
$out->elementStart('li');
|
||||
@ -291,45 +283,59 @@ class BookmarkPlugin extends Plugin
|
||||
$out->elementEnd('li');
|
||||
$out->text(' ');
|
||||
}
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$out->elementStart('li');
|
||||
$out->element('a',
|
||||
array('rel' => 'tag',
|
||||
'href' => Notice_tag::url($tag)),
|
||||
$tag);
|
||||
$out->elementEnd('li');
|
||||
$out->text(' ');
|
||||
}
|
||||
|
||||
$out->elementEnd('ul');
|
||||
}
|
||||
|
||||
$tags = $nli->notice->getTags();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$out->elementStart('li');
|
||||
$out->element('a',
|
||||
array('rel' => 'tag',
|
||||
'href' => Notice_tag::url($tag)),
|
||||
$tag);
|
||||
$out->elementEnd('li');
|
||||
$out->text(' ');
|
||||
if (!empty($nb->description)) {
|
||||
$out->element('p',
|
||||
array('class' => 'bookmark-description'),
|
||||
$nb->description);
|
||||
}
|
||||
|
||||
$out->elementEnd('ul');
|
||||
|
||||
$out->element('p',
|
||||
array('class' => 'bookmark_description'),
|
||||
$nb->description);
|
||||
|
||||
if (common_config('attachments', 'show_thumbs')) {
|
||||
$al = new InlineAttachmentList($notice, $out);
|
||||
$al->show();
|
||||
$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) ?
|
||||
$avatar->displayUrl() :
|
||||
Avatar::defaultImage(AVATAR_MINI_SIZE),
|
||||
'class' => 'avatar photo bookmark_avatar',
|
||||
'width' => AVATAR_MINI_SIZE,
|
||||
'height' => AVATAR_MINI_SIZE,
|
||||
'alt' => $profile->getBestName()));
|
||||
$out->element('img',
|
||||
array('src' => ($avatar) ?
|
||||
$avatar->displayUrl() :
|
||||
Avatar::defaultImage(AVATAR_MINI_SIZE),
|
||||
'class' => 'avatar photo bookmark-avatar',
|
||||
'width' => AVATAR_MINI_SIZE,
|
||||
'height' => AVATAR_MINI_SIZE,
|
||||
'alt' => $profile->getBestName()));
|
||||
|
||||
$out->raw(' ');
|
||||
$out->element('a', array('href' => $profile->profileurl,
|
||||
'title' => $profile->getBestName()),
|
||||
|
||||
$out->element('a',
|
||||
array('href' => $profile->profileurl,
|
||||
'title' => $profile->getBestName()),
|
||||
$profile->nickname);
|
||||
|
||||
$nli->showNoticeLink();
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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();
|
||||
}});
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -61,49 +61,29 @@ 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>."));
|
||||
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;
|
||||
}
|
||||
|
||||
$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,
|
||||
'distribute' => false));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
||||
$this->success = true;
|
||||
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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
===============
|
||||
|
||||
|
108
plugins/FacebookBridge/README
Normal file
108
plugins/FacebookBridge/README
Normal 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.
|
@ -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);
|
||||
}
|
||||
|
||||
|
435
plugins/NewMenu/NewMenuPlugin.php
Normal file
435
plugins/NewMenu/NewMenuPlugin.php
Normal 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;
|
||||
}
|
||||
}
|
47
plugins/NewMenu/newmenu.css
Normal file
47
plugins/NewMenu/newmenu.css
Normal 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;
|
||||
}
|
@ -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()
|
||||
|
@ -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
37
tests/UUIDTest.php
Normal 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");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user