Merge branch 'dev-0.7.x' into framebusting
Conflicts: lib/action.php
This commit is contained in:
commit
cca1d77748
53
README
53
README
|
@ -507,7 +507,7 @@ server is probably a good idea for high-volume sites.
|
|||
needs as a parameter the install path; if you run it from the
|
||||
Laconica dir, "." should suffice.
|
||||
|
||||
This will run six (for now) queue handlers:
|
||||
This will run eight (for now) queue handlers:
|
||||
|
||||
* xmppdaemon.php - listens for new XMPP messages from users and stores
|
||||
them as notices in the database.
|
||||
|
@ -521,6 +521,10 @@ This will run six (for now) queue handlers:
|
|||
of registered users.
|
||||
* xmppconfirmhandler.php - sends confirmation messages to registered
|
||||
users.
|
||||
* twitterqueuehandler.php - sends queued notices to Twitter for user
|
||||
who have opted to set up Twitter bridging.
|
||||
* facebookqueuehandler.php - sends queued notices to Facebook for users
|
||||
of the built-in Facebook application.
|
||||
|
||||
Note that these queue daemons are pretty raw, and need your care. In
|
||||
particular, they leak memory, and you may want to restart them on a
|
||||
|
@ -553,6 +557,53 @@ Sample cron job:
|
|||
# Update Twitter friends subscriptions every half hour
|
||||
0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null
|
||||
|
||||
Built-in Facebook Application
|
||||
-----------------------------
|
||||
|
||||
Laconica's Facebook application allows your users to automatically
|
||||
update their Facebook statuses with their latest notices, invite
|
||||
their friends to use the app (and thus your site), view their notice
|
||||
timelines, and post notices -- all from within Facebook. The application
|
||||
is built into Laconica and runs on your host. For automatic Facebook
|
||||
status updating to work you will need to enable queuing and run the
|
||||
facebookqueuehandler.php daemon (see the "Queues and daemons" section
|
||||
above).
|
||||
|
||||
Quick setup instructions*:
|
||||
|
||||
Install the Facebook Developer application on Facebook:
|
||||
|
||||
http://www.facebook.com/developers/
|
||||
|
||||
Use it to create a new application and generate an API key and secret.
|
||||
Uncomment the Facebook app section of your config.php and copy in the
|
||||
key and secret, e.g.:
|
||||
|
||||
# Config section for the built-in Facebook application
|
||||
$config['facebook']['apikey'] = 'APIKEY';
|
||||
$config['facebook']['secret'] = 'SECRET';
|
||||
|
||||
In Facebook's application editor, specify the following URLs for your app:
|
||||
|
||||
- Callback URL: http://example.net/mublog/facebook/
|
||||
- Post-Remove URL: http://example.net/mublog/facebook/remove
|
||||
- Post-Add Redirect URL: http://apps.facebook.com/yourapp/
|
||||
- Canvas URL: http://apps.facebook.com/yourapp/
|
||||
|
||||
(Replace 'example.net' with your host's URL, 'mublog' with the path
|
||||
to your Laconica installation, and 'yourapp' with the name of the
|
||||
Facebook application you created.)
|
||||
|
||||
Additionally, Choose "Web" for Application type in the Advanced tab.
|
||||
In the "Canvas setting" section, choose the "FBML" for Render Method,
|
||||
"Smart Size" for IFrame size, and "Full width (760px)" for Canvas Width.
|
||||
Everything else can be left with default values.
|
||||
|
||||
*For more detailed instructions please see the installation guide on the
|
||||
Laconica wiki:
|
||||
|
||||
http://laconi.ca/trac/wiki/FacebookApplication
|
||||
|
||||
Sitemaps
|
||||
--------
|
||||
|
||||
|
|
|
@ -164,6 +164,11 @@ class EmailsettingsAction extends AccountSettingsAction
|
|||
$user->emailnotifymsg);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('emailnotifyattn',
|
||||
_('Send me email when someone sends me an "@-reply".'),
|
||||
$user->emailnotifyattn);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('emailnotifynudge',
|
||||
_('Allow friends to nudge me and send me an email.'),
|
||||
$user->emailnotifynudge);
|
||||
|
@ -255,6 +260,7 @@ class EmailsettingsAction extends AccountSettingsAction
|
|||
$emailnotifyfav = $this->boolean('emailnotifyfav');
|
||||
$emailnotifymsg = $this->boolean('emailnotifymsg');
|
||||
$emailnotifynudge = $this->boolean('emailnotifynudge');
|
||||
$emailnotifyattn = $this->boolean('emailnotifyattn');
|
||||
$emailmicroid = $this->boolean('emailmicroid');
|
||||
$emailpost = $this->boolean('emailpost');
|
||||
|
||||
|
@ -270,6 +276,7 @@ class EmailsettingsAction extends AccountSettingsAction
|
|||
$user->emailnotifyfav = $emailnotifyfav;
|
||||
$user->emailnotifymsg = $emailnotifymsg;
|
||||
$user->emailnotifynudge = $emailnotifynudge;
|
||||
$user->emailnotifyattn = $emailnotifyattn;
|
||||
$user->emailmicroid = $emailmicroid;
|
||||
$user->emailpost = $emailpost;
|
||||
|
||||
|
|
|
@ -292,11 +292,11 @@ class ShowstreamAction extends Action
|
|||
$this->elementStart('ul', 'tags xoxo');
|
||||
foreach ($tags as $tag) {
|
||||
$this->elementStart('li');
|
||||
$this->element('span', 'mark_hash', '#');
|
||||
$this->element('a', array('rel' => 'tag',
|
||||
'href' => common_local_url('peopletag',
|
||||
array('tag' => $tag))),
|
||||
$tag);
|
||||
// Avoid space by using raw output.
|
||||
$pt = '<span class="mark_hash">#</span><a rel="tag" href="' .
|
||||
common_local_url('peopletag', array('tag' => $tag)) .
|
||||
'">' . $tag . '</a>';
|
||||
$this->raw($pt);
|
||||
$this->elementEnd('li');
|
||||
}
|
||||
$this->elementEnd('ul');
|
||||
|
|
BIN
bin/flowplayer-3.0.5.swf
Normal file
BIN
bin/flowplayer-3.0.5.swf
Normal file
Binary file not shown.
BIN
bin/flowplayer.audio-3.0.3.swf
Normal file
BIN
bin/flowplayer.audio-3.0.3.swf
Normal file
Binary file not shown.
BIN
bin/flowplayer.controls-3.0.3.swf
Normal file
BIN
bin/flowplayer.controls-3.0.3.swf
Normal file
Binary file not shown.
|
@ -34,22 +34,23 @@ class Notice extends Memcached_DataObject
|
|||
###START_AUTOCODE
|
||||
/* the code below is auto generated do not remove the above tag */
|
||||
|
||||
public $__table = 'notice'; // table name
|
||||
public $id; // int(4) primary_key not_null
|
||||
public $profile_id; // int(4) not_null
|
||||
public $__table = 'notice'; // table name
|
||||
public $id; // int(4) primary_key not_null
|
||||
public $profile_id; // int(4) not_null
|
||||
public $uri; // varchar(255) unique_key
|
||||
public $content; // varchar(140)
|
||||
public $rendered; // text()
|
||||
public $rendered; // text()
|
||||
public $url; // varchar(255)
|
||||
public $created; // datetime() not_null
|
||||
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
||||
public $reply_to; // int(4)
|
||||
public $is_local; // tinyint(1)
|
||||
public $source; // varchar(32)
|
||||
public $created; // datetime() not_null
|
||||
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
||||
public $reply_to; // int(4)
|
||||
public $is_local; // tinyint(1)
|
||||
public $source; // varchar(32)
|
||||
|
||||
/* Static get */
|
||||
function staticGet($k,$v=null)
|
||||
{ return Memcached_DataObject::staticGet('Notice',$k,$v); }
|
||||
function staticGet($k,$v=NULL) {
|
||||
return Memcached_DataObject::staticGet('Notice',$k,$v);
|
||||
}
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
@ -94,23 +95,28 @@ class Notice extends Memcached_DataObject
|
|||
/* Add them to the database */
|
||||
foreach(array_unique($match[1]) as $hashtag) {
|
||||
/* elide characters we don't want in the tag */
|
||||
$hashtag = common_canonical_tag($hashtag);
|
||||
|
||||
$tag = DB_DataObject::factory('Notice_tag');
|
||||
$tag->notice_id = $this->id;
|
||||
$tag->tag = $hashtag;
|
||||
$tag->created = $this->created;
|
||||
$id = $tag->insert();
|
||||
if (!$id) {
|
||||
$last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
common_log(LOG_ERR, 'DB error inserting hashtag: ' . $last_error->message);
|
||||
common_server_error(sprintf(_('DB error inserting hashtag: %s'), $last_error->message));
|
||||
return;
|
||||
}
|
||||
$this->saveTag($hashtag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveTag($hashtag)
|
||||
{
|
||||
$hashtag = common_canonical_tag($hashtag);
|
||||
|
||||
$tag = new Notice_tag();
|
||||
$tag->notice_id = $this->id;
|
||||
$tag->tag = $hashtag;
|
||||
$tag->created = $this->created;
|
||||
$id = $tag->insert();
|
||||
|
||||
if (!$id) {
|
||||
throw new ServerException(sprintf(_('DB error inserting hashtag: %s'),
|
||||
$last_error->message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static function saveNew($profile_id, $content, $source=null, $is_local=1, $reply_to=null, $uri=null) {
|
||||
|
||||
$profile = Profile::staticGet($profile_id);
|
||||
|
@ -136,10 +142,12 @@ class Notice extends Memcached_DataObject
|
|||
$notice->profile_id = $profile_id;
|
||||
|
||||
$blacklist = common_config('public', 'blacklist');
|
||||
$autosource = common_config('public', 'autosource');
|
||||
|
||||
# Blacklisted are non-false, but not 1, either
|
||||
|
||||
if ($blacklist && in_array($profile_id, $blacklist)) {
|
||||
if (($blacklist && in_array($profile_id, $blacklist)) ||
|
||||
($source && $autosource && in_array($source, $autosource))) {
|
||||
$notice->is_local = -1;
|
||||
} else {
|
||||
$notice->is_local = $is_local;
|
||||
|
@ -619,6 +627,15 @@ class Notice extends Memcached_DataObject
|
|||
continue;
|
||||
}
|
||||
|
||||
// we automatically add a tag for every group name, too
|
||||
|
||||
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($nickname),
|
||||
'notice_id' => $this->id));
|
||||
|
||||
if (is_null($tag)) {
|
||||
$this->saveTag($nickname);
|
||||
}
|
||||
|
||||
if ($profile->isMember($group)) {
|
||||
|
||||
$gi = new Group_inbox();
|
||||
|
@ -730,10 +747,19 @@ class Notice extends Memcached_DataObject
|
|||
if (!$id) {
|
||||
common_log_db_error($reply, 'INSERT', __FILE__);
|
||||
return;
|
||||
} else {
|
||||
$replied[$recipient->id] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_keys($replied) as $recipient) {
|
||||
$user = User::staticGet('id', $recipient);
|
||||
if ($user) {
|
||||
mail_notify_attn($user, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
||||
|
||||
class Notice_tag extends Memcached_DataObject
|
||||
class Notice_tag extends Memcached_DataObject
|
||||
{
|
||||
###START_AUTOCODE
|
||||
/* the code below is auto generated do not remove the above tag */
|
||||
|
@ -35,9 +35,9 @@ class Notice_tag extends Memcached_DataObject
|
|||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
|
||||
static function getStream($tag, $offset=0, $limit=20) {
|
||||
$qry =
|
||||
$qry =
|
||||
'SELECT notice.* ' .
|
||||
'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' .
|
||||
'WHERE notice_tag.tag = "%s" ';
|
||||
|
@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
|
|||
'notice_tag:notice_stream:' . common_keyize($tag),
|
||||
$offset, $limit);
|
||||
}
|
||||
|
||||
|
||||
function blowCache()
|
||||
{
|
||||
$cache = common_memcache();
|
||||
|
@ -54,4 +54,9 @@ class Notice_tag extends Memcached_DataObject
|
|||
$cache->delete(common_cache_key('notice_tag:notice_stream:' . $this->tag));
|
||||
}
|
||||
}
|
||||
|
||||
function &pkeyGet($kv)
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('Notice_tag', $kv);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ class User extends Memcached_DataObject
|
|||
public $emailnotifyfav; // tinyint(1) default_1
|
||||
public $emailnotifynudge; // tinyint(1) default_1
|
||||
public $emailnotifymsg; // tinyint(1) default_1
|
||||
public $emailnotifyattn; // tinyint(1) default_1
|
||||
public $emailmicroid; // tinyint(1) default_1
|
||||
public $language; // varchar(50)
|
||||
public $timezone; // varchar(50)
|
||||
|
@ -62,8 +63,10 @@ class User extends Memcached_DataObject
|
|||
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
|
||||
|
||||
/* Static get */
|
||||
function staticGet($k,$v=null)
|
||||
{ return Memcached_DataObject::staticGet('User',$k,$v); }
|
||||
function staticGet($k,$v=NULL)
|
||||
{
|
||||
return Memcached_DataObject::staticGet('User',$k,$v);
|
||||
}
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
|
|
@ -332,6 +332,7 @@ emailnotifysub = 17
|
|||
emailnotifyfav = 17
|
||||
emailnotifynudge = 17
|
||||
emailnotifymsg = 17
|
||||
emailnotifyattn = 17
|
||||
emailmicroid = 17
|
||||
language = 2
|
||||
timezone = 2
|
||||
|
|
|
@ -18,6 +18,8 @@ $config['site']['server'] = 'localhost';
|
|||
$config['site']['path'] = 'laconica';
|
||||
#$config['site']['fancy'] = false;
|
||||
#$config['site']['theme'] = 'default';
|
||||
#To enable the built-in mobile style sheet, defaults to false.
|
||||
#$config['site']['mobile'] = true;
|
||||
#For contact email, defaults to $_SERVER["SERVER_ADMIN"]
|
||||
#$config['site']['email'] = 'admin@example.net';
|
||||
#Brought by...
|
||||
|
@ -107,6 +109,14 @@ $config['sphinx']['port'] = 3312;
|
|||
#$config['public']['blacklist'][] = 123;
|
||||
#$config['public']['blacklist'][] = 2307;
|
||||
|
||||
#Mark certain notice sources as automatic and thus not
|
||||
#appropriate for public feed
|
||||
#$config['public]['autosource'][] = 'twitterfeed';
|
||||
#$config['public]['autosource'][] = 'rssdent';
|
||||
#$config['public]['autosource'][] = 'Ping.Fm';
|
||||
#$config['public]['autosource'][] = 'HelloTxt';
|
||||
#$config['public]['autosource'][] = 'Updating.Me';
|
||||
|
||||
#Do notice broadcasts offline
|
||||
#If you use this, you must run the six offline daemons in the
|
||||
#background. See the README for details.
|
||||
|
@ -139,7 +149,7 @@ $config['sphinx']['port'] = 3312;
|
|||
#$config['profile']['banned'][] = 'hacker';
|
||||
#$config['profile']['banned'][] = 12345;
|
||||
|
||||
# config section for the built-in Facebook application
|
||||
# Config section for the built-in Facebook application
|
||||
#$config['facebook']['apikey'] = 'APIKEY';
|
||||
#$config['facebook']['secret'] = 'SECRET';
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ create table user (
|
|||
emailnotifyfav tinyint default 1 comment 'Notify by email of favorites',
|
||||
emailnotifynudge tinyint default 1 comment 'Notify by email of nudges',
|
||||
emailnotifymsg tinyint default 1 comment 'Notify by email of direct messages',
|
||||
emailnotifyattn tinyint default 1 comment 'Notify by email of @-replies',
|
||||
emailmicroid tinyint default 1 comment 'whether to publish email microid',
|
||||
language varchar(50) comment 'preferred language',
|
||||
timezone varchar(50) comment 'timezone',
|
||||
|
|
24
js/flowplayer-3.0.5.min.js
vendored
Normal file
24
js/flowplayer-3.0.5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
js/jquery.simplemodal-1.2.2.pack.js
Normal file
8
js/jquery.simplemodal-1.2.2.pack.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* SimpleModal 1.2.2 - jQuery Plugin
|
||||
* http://www.ericmmartin.com/projects/simplemodal/
|
||||
* Copyright (c) 2008 Eric Martin
|
||||
* Dual licensed under the MIT and GPL licenses
|
||||
* Revision: $Id: jquery.simplemodal.js 181 2008-12-16 16:51:44Z emartin24 $
|
||||
*/
|
||||
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(g($){m f=$.Q.1Q&&1a($.Q.1D)==6&&!10[\'2g\'],1f=$.Q.1Q&&!$.2a,w=[];$.y=g(a,b){I $.y.12.1n(a,b)};$.y.D=g(){$.y.12.D()};$.1P.y=g(a){I $.y.12.1n(3,a)};$.y.1O={V:29,1J:\'r-H\',1B:{},1z:\'r-n\',20:{},1Z:{},v:2t,D:1o,1T:\'<a 2j="2h" 2f="2e"></a>\',X:\'r-D\',l:F,1g:K,1e:F,1d:F,1c:F};$.y.12={7:F,4:{},1n:g(a,b){8(3.4.j){I K}3.7=$.U({},$.y.1O,b);3.v=3.7.v;3.1w=K;8(J a==\'27\'){a=a 25 1A?a:$(a);8(a.1v().1v().23()>0){3.4.T=a.1v();8(!3.7.1g){3.4.21=a.2x(1o)}}}q 8(J a==\'2w\'||J a==\'1r\'){a=$(\'<1q/>\').2s(a)}q{2r(\'2q 2p: 2o j 2l: \'+J a);I K}3.4.j=a.11(\'r-j\').E(3.7.1Z);a=F;3.1S();3.1R();8($.1m(3.7.1d)){3.7.1d.1l(3,[3.4])}I 3},1S:g(){w=3.1k();8(f){3.4.x=$(\'<x 2d="2c:K;"/>\').E($.U(3.7.2b,{1j:\'1i\',V:0,l:\'1h\',A:w[0],z:w[1],v:3.7.v,L:0,B:0})).O(\'u\')}3.4.H=$(\'<1q/>\').1N(\'1M\',3.7.1J).11(\'r-H\').E($.U(3.7.1B,{1j:\'1i\',V:3.7.V/1b,A:w[0],z:w[1],l:\'1h\',B:0,L:0,v:3.7.v+1})).O(\'u\');3.4.n=$(\'<1q/>\').1N(\'1M\',3.7.1z).11(\'r-n\').E($.U(3.7.20,{1j:\'1i\',l:\'1h\',v:3.7.v+2})).1K(3.7.D?$(3.7.1T).11(3.7.X):\'\').O(\'u\');3.19();8(f||1f){3.18()}3.4.n.1K(3.4.j.1I())},1H:g(){m a=3;$(\'.\'+3.7.X).1G(\'1L.r\',g(e){e.28();a.D()});$(10).1G(\'1F.r\',g(){w=a.1k();a.19();8(f||1f){a.18()}q{a.4.x&&a.4.x.E({A:w[0],z:w[1]});a.4.H.E({A:w[0],z:w[1]})}})},1E:g(){$(\'.\'+3.7.X).1C(\'1L.r\');$(10).1C(\'1F.r\')},18:g(){m p=3.7.l;$.26([3.4.x||F,3.4.H,3.4.n],g(i,e){8(e){m a=\'k.u.17\',N=\'k.u.1W\',16=\'k.u.24\',S=\'k.u.1y\',R=\'k.u.1x\',15=\'k.u.22\',1t=\'k.P.17\',1s=\'k.P.1W\',C=\'k.P.1y\',G=\'k.P.1x\',s=e[0].2v;s.l=\'2u\';8(i<2){s.14(\'A\');s.14(\'z\');s.Z(\'A\',\'\'+16+\' > \'+a+\' ? \'+16+\' : \'+a+\' + "o"\');s.Z(\'z\',\'\'+15+\' > \'+N+\' ? \'+15+\' : \'+N+\' + "o"\')}q{m b,W;8(p&&p.1Y==1X){8(p[0]){m c=J p[0]==\'1r\'?p[0].1V():p[0].13(/o/,\'\');b=c.1U(\'%\')==-1?c+\' + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\':1a(c.13(/%/,\'\'))+\' * ((\'+1t+\' || \'+a+\') / 1b) + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\'}8(p[1]){m d=J p[1]==\'1r\'?p[1].1V():p[1].13(/o/,\'\');W=d.1U(\'%\')==-1?d+\' + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\':1a(d.13(/%/,\'\'))+\' * ((\'+1s+\' || \'+N+\') / 1b) + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\'}}q{b=\'(\'+1t+\' || \'+a+\') / 2 - (3.2n / 2) + (t = \'+G+\' ? \'+G+\' : \'+R+\') + "o"\';W=\'(\'+1s+\' || \'+N+\') / 2 - (3.2m / 2) + (t = \'+C+\' ? \'+C+\' : \'+S+\') + "o"\'}s.14(\'L\');s.14(\'B\');s.Z(\'L\',b);s.Z(\'B\',W)}}})},1k:g(){m a=$(10);m h=$.Q.2k&&$.Q.1D>\'9.5\'&&$.1P.2i<=\'1.2.6\'?k.P[\'17\']:a.A();I[h,a.z()]},19:g(){m a,B,1u=(w[0]/2)-((3.4.n.A()||3.4.j.A())/2),1p=(w[1]/2)-((3.4.n.z()||3.4.j.z())/2);8(3.7.l&&3.7.l.1Y==1X){a=3.7.l[0]||1u;B=3.7.l[1]||1p}q{a=1u;B=1p}3.4.n.E({B:B,L:a})},1R:g(){3.4.x&&3.4.x.Y();8($.1m(3.7.1e)){3.7.1e.1l(3,[3.4])}q{3.4.H.Y();3.4.n.Y();3.4.j.Y()}3.1H()},D:g(){8(!3.4.j){I K}8($.1m(3.7.1c)&&!3.1w){3.1w=1o;3.7.1c.1l(3,[3.4])}q{8(3.4.T){8(3.7.1g){3.4.j.1I().O(3.4.T)}q{3.4.j.M();3.4.21.O(3.4.T)}}q{3.4.j.M()}3.4.n.M();3.4.H.M();3.4.x&&3.4.x.M();3.4={}}3.1E()}}})(1A);',62,158,'|||this|dialog|||opts|if||||||||function|||data|document|position|var|container|px||else|simplemodal|||body|zIndex||iframe|modal|width|height|left|sl|close|css|null|st|overlay|return|typeof|false|top|remove|bcw|appendTo|documentElement|browser|bst|bsl|parentNode|extend|opacity|le|closeClass|show|setExpression|window|addClass|impl|replace|removeExpression|bsw|bsh|clientHeight|fixIE|setPosition|parseInt|100|onClose|onShow|onOpen|ieQuirks|persist|fixed|none|display|getDimensions|apply|isFunction|init|true|vCenter|div|number|cw|ch|hCenter|parent|occb|scrollTop|scrollLeft|containerId|jQuery|overlayCss|unbind|version|unbindEvents|resize|bind|bindEvents|hide|overlayId|append|click|id|attr|defaults|fn|msie|open|create|closeHTML|indexOf|toString|clientWidth|Array|constructor|dataCss|containerCss|orig|scrollWidth|size|scrollHeight|instanceof|each|object|preventDefault|50|boxModel|iframeCss|javascript|src|Close|title|XMLHttpRequest|modalCloseImg|jquery|class|opera|type|offsetWidth|offsetHeight|Unsupported|Error|SimpleModal|alert|html|1000|absolute|style|string|clone'.split('|'),0,{}))
|
9
js/video.js
Normal file
9
js/video.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
$('document').ready(function() {
|
||||
$('a.media, a.mediamp3').append(' <sup>[PLAY]</sup>');
|
||||
$('a.mediamp3').html('').css('display', 'block').css('width', '224px').css('height','24px').flowplayer('../bin/flowplayer-3.0.5.swf');
|
||||
$('a.media').click(function() {
|
||||
$('<a id="p1i"></a>').attr('href', $(this).attr('href')).flowplayer('../bin/flowplayer-3.0.5.swf').modal({'closeHTML':'<a class="modalCloseImg" title="Close"><img src="x.png" /></a>'});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
|
@ -153,14 +153,26 @@ class Action extends HTMLOutputter // lawsuit
|
|||
{
|
||||
if (Event::handle('StartShowStyles', array($this))) {
|
||||
if (Event::handle('StartShowLaconicaStyles', array($this))) {
|
||||
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/display.css', 'base') . '?version=' . LACONICA_VERSION,
|
||||
'media' => 'screen, projection, tv'));
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/modal.css', 'base') . '?version=' . LACONICA_VERSION,
|
||||
'media' => 'screen, projection, tv'));
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
|
||||
'media' => 'screen, projection, tv'));
|
||||
if (common_config('site', 'mobile')) {
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
|
||||
// TODO: "handheld" CSS for other mobile devices
|
||||
'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
|
||||
}
|
||||
Event::handle('EndShowLaconicaStyles', array($this));
|
||||
}
|
||||
if (Event::handle('StartShowUAStyles', array($this))) {
|
||||
|
@ -196,6 +208,13 @@ class Action extends HTMLOutputter // lawsuit
|
|||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/jquery.form.js')),
|
||||
' ');
|
||||
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/jquery.simplemodal-1.2.2.pack.js')),
|
||||
' ');
|
||||
|
||||
|
||||
Event::handle('EndShowJQueryScripts', array($this));
|
||||
}
|
||||
if (Event::handle('StartShowLaconicaScripts', array($this))) {
|
||||
|
@ -208,6 +227,14 @@ class Action extends HTMLOutputter // lawsuit
|
|||
// Frame-busting code to avoid clickjacking attacks.
|
||||
$this->element('script', array('type' => 'text/javascript'),
|
||||
'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/flowplayer-3.0.5.min.js')),
|
||||
' ');
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/video.js')),
|
||||
' ');
|
||||
Event::handle('EndShowLaconicaScripts', array($this));
|
||||
}
|
||||
Event::handle('EndShowScripts', array($this));
|
||||
|
|
|
@ -106,7 +106,8 @@ $config =
|
|||
array('server' => null),
|
||||
'public' =>
|
||||
array('localonly' => true,
|
||||
'blacklist' => array()),
|
||||
'blacklist' => array(),
|
||||
'autosource' => array()),
|
||||
'theme' =>
|
||||
array('server' => null),
|
||||
'throttle' =>
|
||||
|
|
50
lib/mail.php
50
lib/mail.php
|
@ -573,3 +573,53 @@ function mail_notify_fave($other, $user, $notice)
|
|||
common_init_locale();
|
||||
mail_to_user($other, $subject, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* notify a user that they have received an "attn:" message AKA "@-reply"
|
||||
*
|
||||
* @param User $user The user who recevied the notice
|
||||
* @param Notice $notice The notice that was sent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function mail_notify_attn($user, $notice)
|
||||
{
|
||||
if (!$user->email || !$user->emailnotifyattn) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sender = $notice->getProfile();
|
||||
|
||||
$bestname = $sender->getBestName();
|
||||
|
||||
common_init_locale($user->language);
|
||||
|
||||
$subject = sprintf(_('%s sent a notice to your attention'), $bestname);
|
||||
|
||||
$body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
|
||||
"The notice is here:\n\n".
|
||||
"\t%3\$s\n\n" .
|
||||
"It reads:\n\n".
|
||||
"\t%4\$s\n\n" .
|
||||
"You can reply back here:\n\n".
|
||||
"\t%5\$s\n\n" .
|
||||
"The list of all @-replies for you here:\n\n" .
|
||||
"%6\$s\n\n" .
|
||||
"Faithfully yours,\n" .
|
||||
"%2\$s\n\n" .
|
||||
"P.S. You can turn off these email notifications here: %7\$s\n"),
|
||||
$bestname,
|
||||
common_config('site', 'name'),
|
||||
common_local_url('shownotice',
|
||||
array('notice' => $notice->id)),
|
||||
$notice->content,
|
||||
common_local_url('newnotice',
|
||||
array('replyto' => $sender->nickname)),
|
||||
common_local_url('replies',
|
||||
array('nickname' => $user->nickname)),
|
||||
common_local_url('emailsettings'));
|
||||
|
||||
common_init_locale();
|
||||
mail_to_user($user, $subject, $body);
|
||||
}
|
||||
|
|
|
@ -474,12 +474,18 @@ function common_replace_urls_callback($text, $callback) {
|
|||
function common_linkify($url) {
|
||||
// It comes in special'd, so we unspecial it before passing to the stringifying
|
||||
// functions
|
||||
$ext = pathinfo($url, PATHINFO_EXTENSION);
|
||||
$url = htmlspecialchars_decode($url);
|
||||
$video_ext = array('mp4', 'flv', 'avi', 'mpg', 'mp3', 'ogg');
|
||||
$display = $url;
|
||||
$url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
|
||||
|
||||
$attrs = array('href' => $url, 'rel' => 'external');
|
||||
|
||||
if (in_array($ext, $video_ext)) {
|
||||
$attrs['class'] = 'media';
|
||||
}
|
||||
|
||||
if ($longurl = common_longurl($url)) {
|
||||
$attrs['title'] = $longurl;
|
||||
}
|
||||
|
@ -590,7 +596,7 @@ function common_tag_link($tag)
|
|||
$xs->element('a', array('href' => $url,
|
||||
'rel' => 'tag'),
|
||||
$tag);
|
||||
$xs->elementEnd();
|
||||
$xs->elementEnd('span');
|
||||
return $xs->getString();
|
||||
}
|
||||
|
||||
|
|
144
plugins/BlogspamNetPlugin.php
Normal file
144
plugins/BlogspamNetPlugin.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Plugin to check submitted notices with blogspam.net
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
define('BLOGSPAMNETPLUGIN_VERSION', '0.1');
|
||||
|
||||
/**
|
||||
* Plugin to check submitted notices with blogspam.net
|
||||
*
|
||||
* When new notices are saved, we check their text with blogspam.net (or
|
||||
* a compatible service).
|
||||
*
|
||||
* Blogspam.net is supposed to catch blog comment spam, and I found that
|
||||
* some of its tests (min/max size, bayesian match) gave a lot of false positives.
|
||||
* So, I've turned those tests off by default. This may not get as many
|
||||
* hits, but it's better than nothing.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
* @see Event
|
||||
*/
|
||||
|
||||
class BlogspamNetPlugin extends Plugin
|
||||
{
|
||||
var $baseUrl = 'http://test.blogspam.net:8888/';
|
||||
|
||||
function __construct($url=null)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($url) {
|
||||
$this->baseUrl = $url;
|
||||
}
|
||||
}
|
||||
|
||||
function onStartNoticeSave($notice)
|
||||
{
|
||||
$args = $this->testArgs($notice);
|
||||
common_debug("Blogspamnet args = " . print_r($args, TRUE));
|
||||
$request = xmlrpc_encode_request('testComment', array($args));
|
||||
$context = stream_context_create(array('http' => array('method' => "POST",
|
||||
'header' =>
|
||||
"Content-Type: text/xml\r\n".
|
||||
"User-Agent: " . $this->userAgent(),
|
||||
'content' => $request)));
|
||||
$file = file_get_contents($this->baseUrl, false, $context);
|
||||
$response = xmlrpc_decode($file);
|
||||
if (xmlrpc_is_fault($response)) {
|
||||
throw new ServerException("$response[faultString] ($response[faultCode])", 500);
|
||||
} else {
|
||||
common_debug("Blogspamnet results = " . $response);
|
||||
if (preg_match('/^ERROR(:(.*))?$/', $response, $match)) {
|
||||
throw new ServerException(sprintf(_("Error from %s: %s"), $this->baseUrl, $match[2]), 500);
|
||||
} else if (preg_match('/^SPAM(:(.*))?$/', $response, $match)) {
|
||||
throw new ClientException(sprintf(_("Spam checker results: %s"), $match[2]), 400);
|
||||
} else if (preg_match('/^OK$/', $response)) {
|
||||
// don't do anything
|
||||
} else {
|
||||
throw new ServerException(sprintf(_("Unexpected response from %s: %s"), $this->baseUrl, $response), 500);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function testArgs($notice)
|
||||
{
|
||||
$args = array();
|
||||
$args['comment'] = $notice->content;
|
||||
$args['ip'] = $this->getClientIP();
|
||||
|
||||
if (isset($_SERVER) && array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
|
||||
$args['agent'] = $_SERVER['HTTP_USER_AGENT'];
|
||||
}
|
||||
|
||||
$profile = $notice->getProfile();
|
||||
|
||||
if ($profile && $profile->homepage) {
|
||||
$args['link'] = $profile->homepage;
|
||||
}
|
||||
|
||||
if ($profile && $profile->fullname) {
|
||||
$args['name'] = $profile->fullname;
|
||||
} else {
|
||||
$args['name'] = $profile->nickname;
|
||||
}
|
||||
|
||||
$args['site'] = common_root_url();
|
||||
$args['version'] = $this->userAgent();
|
||||
|
||||
$args['options'] = "max-size=140,min-size=0,min-words=0,exclude=bayasian";
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
function getClientIP()
|
||||
{
|
||||
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
||||
// Note: order matters here; use proxy-forwarded stuff first
|
||||
foreach (array('HTTP_X_FORWARDED_FOR', 'CLIENT-IP', 'REMOTE_ADDR') as $k) {
|
||||
if (isset($_SERVER[$k])) {
|
||||
return $_SERVER[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
||||
function userAgent()
|
||||
{
|
||||
return 'BlogspamNetPlugin/'.BLOGSPAMNETPLUGIN_VERSION . ' Laconica/' . LACONICA_VERSION;
|
||||
}
|
||||
}
|
71
scripts/facebookqueuehandler.php
Executable file
71
scripts/facebookqueuehandler.php
Executable file
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
/*
|
||||
* Laconica - a distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, Controlez-Vous, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
# Abort if called from a web server
|
||||
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('LACONICA', true);
|
||||
|
||||
require_once(INSTALLDIR . '/lib/common.php');
|
||||
require_once(INSTALLDIR . '/lib/facebookutil.php');
|
||||
require_once(INSTALLDIR . '/lib/queuehandler.php');
|
||||
|
||||
set_error_handler('common_error_handler');
|
||||
|
||||
class FacebookQueueHandler extends QueueHandler
|
||||
{
|
||||
|
||||
function transport()
|
||||
{
|
||||
return 'facebook';
|
||||
}
|
||||
|
||||
function start()
|
||||
{
|
||||
$this->log(LOG_INFO, "INITIALIZE");
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle_notice($notice)
|
||||
{
|
||||
return facebookBroadcastNotice($notice);
|
||||
}
|
||||
|
||||
function finish()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ini_set("max_execution_time", "0");
|
||||
ini_set("max_input_time", "0");
|
||||
set_time_limit(0);
|
||||
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
$id = ($argc > 1) ? $argv[1] : null;
|
||||
|
||||
$handler = new FacebookQueueHandler($id);
|
||||
|
||||
$handler->runOnce();
|
|
@ -22,7 +22,6 @@ line-height:1.65;
|
|||
position:relative;
|
||||
}
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
text-transform:capitalize;
|
||||
margin-bottom:7px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
|
72
theme/base/css/mobile.css
Normal file
72
theme/base/css/mobile.css
Normal file
|
@ -0,0 +1,72 @@
|
|||
/** theme: base
|
||||
*
|
||||
* @package Laconica
|
||||
* @author Meitar Moscovitz <meitar@maymay.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
/* Go linear. */
|
||||
#header,
|
||||
#header address,
|
||||
#site_nav_global_primary,
|
||||
#anon_notice,
|
||||
#site_nav_local_views .nav,
|
||||
#form_notice,
|
||||
#form_notice .form_data li,
|
||||
#core,
|
||||
#content_inner,
|
||||
#notices_primary,
|
||||
.notice,
|
||||
.notice .entry-title,
|
||||
.notice div.entry-content,
|
||||
.notice-options,
|
||||
.notice .notice-options a,
|
||||
.pagination,
|
||||
.pagination .nav,
|
||||
.aside .section { float: none; }
|
||||
|
||||
.notice-options .notice_reply,
|
||||
.notice-options .notice_delete,
|
||||
.notice-options .form_favor,
|
||||
.notice-options .form_disfavor { position: static; }
|
||||
|
||||
#form_notice,
|
||||
#anon_notice,
|
||||
#content_inner,
|
||||
#footer { width: auto; }
|
||||
|
||||
/* And liquid. */
|
||||
#wrap { width: 95%; }
|
||||
|
||||
/* Make things bigger on smaller screens. */
|
||||
body { font-size: 2em; }
|
||||
.notices { font-size: 1.5em; }
|
||||
|
||||
#site_nav_global_primary, #site_nav_global_secondary { text-align: center; }
|
||||
|
||||
.notice div.entry-content { margin-left: 0; }
|
||||
address { margin: 0; }
|
||||
|
||||
#anon_notice, #footer { clear: left; font-size: .5em; }
|
||||
|
||||
#form_notice textarea { width: 80%; height: 5em; }
|
||||
#form_notice .form_note { right: 20%; top: 6em; }
|
||||
#form_notice .form_actions input.submit { width: auto; }
|
||||
|
||||
#content { padding: 18px 0; width: 100%; }
|
||||
#content h1, #page_notice, #content_inner { padding: 0 18px; }
|
||||
.notices .entry-title, .notices div.entry-content { width: 90%; }
|
||||
.notice .author .photo { height: 4.5em; width: 4.5em; } /* about double physical size; TODO: do this scaling better */
|
||||
.notice-options { position: absolute; top: 0; right: 0; padding-left: 7%; width: 3%; }
|
||||
.notice-options .notice_delete a { float: left; } /* Works, but feels like it shouldn't. */
|
||||
/* TODO: Make the icons of the notice options bigger. Probably with mobile-specific images. */
|
||||
.pagination .nav { overflow: auto; }
|
||||
|
||||
#aside_primary { margin: 10px 0 0 0; border: none; padding: 0; width: 100%; }
|
||||
#popular_notices { float: none; width: auto; }
|
||||
/* Columns for supplemental info. */
|
||||
.aside .section { clear: none; padding: 9px; width: 45%; }
|
||||
#top_groups_by_post { float: left; }
|
||||
#featured_users { float: right; }
|
||||
#export_data { display: none; }
|
22
theme/base/css/modal.css
Normal file
22
theme/base/css/modal.css
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SimpleModal Basic Modal Dialog
|
||||
* http://www.ericmmartin.com/projects/simplemodal/
|
||||
* http://code.google.com/p/simplemodal/
|
||||
*
|
||||
* Copyright (c) 2008 Eric Martin - http://ericmmartin.com
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Revision: $Id: basic.css 162 2008-12-01 23:36:58Z emartin24 $
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/* Overlay */
|
||||
#simplemodal-overlay {background-color:#000; cursor:wait;}
|
||||
|
||||
/* Container */
|
||||
#simplemodal-container {height:240px; width:320px; background-color:#fff; border:3px solid #ccc;}
|
||||
#simplemodal-container a.modalCloseImg {background:url(../images/x.png) no-repeat; width:25px; height:29px; display:inline; z-index:3200; position:absolute; top:-15px; right:-18px; cursor:pointer;}
|
||||
#simplemodal-container #basicModalContent {padding:8px;}
|
16
theme/base/css/modal_ie.css
Normal file
16
theme/base/css/modal_ie.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SimpleModal Basic Modal Dialog
|
||||
* http://www.ericmmartin.com/projects/simplemodal/
|
||||
* http://code.google.com/p/simplemodal/
|
||||
*
|
||||
* Copyright (c) 2008 Eric Martin - http://ericmmartin.com
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Revision: $Id: basic_ie.css 162 2008-12-01 23:36:58Z emartin24 $
|
||||
*
|
||||
*/
|
||||
|
||||
/* IE 6 hacks*/
|
||||
#simplemodal-container a.modalCloseImg {background:none; right:-14px; width:22px; height:26px; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/x.png',sizingMethod='scale');}
|
BIN
theme/base/images/x.png
Normal file
BIN
theme/base/images/x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Loading…
Reference in New Issue
Block a user