summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShashi Gowda <connect2shashi@gmail.com>2010-06-09 23:44:32 -0400
committerShashi Gowda <connect2shashi@gmail.com>2010-06-12 12:02:17 -0400
commite7dc786b12638b0c7e4f3ba54308daa3adc4e8a6 (patch)
treed76c8d6bd73014e3578c4838067852871730a44c
parentf6d46870516d6ad95d38917c0fe669556197a3b6 (diff)
SocialLayoutPlugin - the design and javascript for GNUSocial.
Abstracted NewSocialObject, SocialObjectForm, Autocomplete etc. SocialObjectPlugin - abstracts out some code for social-objects (i.e one of videos, photos, links events etc.) TODO: - Autoload files
-rw-r--r--README.branch14
-rw-r--r--plugins/SocialLayout/SocialAutocompleteAction.php127
-rw-r--r--plugins/SocialLayout/SocialLayoutPlugin.php60
-rw-r--r--plugins/SocialLayout/SocialObjectForm.php141
-rw-r--r--plugins/SocialLayout/layout.css4
-rw-r--r--plugins/SocialLayout/layout.js13
-rw-r--r--plugins/SocialObject/NewSocialObjectAction.php210
-rw-r--r--plugins/SocialObject/SocialObject.php324
-rw-r--r--plugins/SocialObject/SocialObjectPlugin.php164
-rw-r--r--plugins/SocialObject/todo8
10 files changed, 1065 insertions, 0 deletions
diff --git a/README.branch b/README.branch
new file mode 100644
index 000000000..6bdfad886
--- /dev/null
+++ b/README.branch
@@ -0,0 +1,14 @@
+
+* call Videos, Photos, Events collectively as social-objects.
+
+* make Video, Photo, Event social-objects subsets of the Notice object.
+ i.e for example, a Video is just extra metadata for a Notice, this lets
+ the core handle all book-keeping for videos too in the name of Notices.
+
+* extend the notice textarea to house facebook status-box-like features,
+ letting users switch between status, video, photo, event sharing modes.
+
+* make a framework for these things. for example, it should be easy to
+ implement a blog-post social-object or a last.fm scrobble social-object
+ by extending a core social-object plugin.
+
diff --git a/plugins/SocialLayout/SocialAutocompleteAction.php b/plugins/SocialLayout/SocialAutocompleteAction.php
new file mode 100644
index 000000000..1f82548fd
--- /dev/null
+++ b/plugins/SocialLayout/SocialAutocompleteAction.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Show JSON for autocompleting replies (usernames) and groups form fields
+ *
+ * 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 GNUSocial
+ * @package StatusNet
+ * @author Shashi Gowda <connect2shashi@gmail.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://daisycha.in
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class SocialAutocompleteAction extends Action
+{
+ var $list=array();
+ var $user;
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ header('Content-Type: application/json; charset=utf-8');
+ if(common_logged_in()) {
+ $user = common_current_user();
+
+ switch($this->trimmed('type')) {
+ case 'users':
+ $this->getUsers($this->trimmed('q'));
+ case 'groups':
+ $this->getGroups($this->trimmed('q'));
+ default
+ return false;
+ }
+ }
+ else {
+ exit(1);
+ }
+ }
+
+ function handle()
+ {
+ parent::handle();
+ // Check for JSONP callback
+ $callback = $this->arg('callback');
+ if ($callback) {
+ print $callback . '(';
+ }
+
+ print json_encode($this->list);
+
+ if ($callback) {
+ print $callback . ')';
+ }
+ }
+
+ function getUsers($q)
+ {
+ $profile = $this->user->getProfile();
+ # show only subscriptions
+ $subs = $profile->getSubscriptions();
+
+ $q = strtolower($q);
+ while($subs->fetch()) {
+ # if $q is empty return everything
+ if(empty($q) ||
+ strpos(strtolower($subs->nickname), $q)) {
+
+ $meta=array();
+
+ $meta['id'] = $subs->id;
+ $meta['nickname'] = $subs->nickname;
+ $meta['name'] = $subs->fullname;
+ $meta['uri'] = $subs->getUri();
+ $meta['avatar'] = $subs->getAvatar(AVATAR_MINI_SIZE);
+
+ $this->list[] = clone($meta);
+ }
+ }
+ $subs->free();
+ }
+
+ function getGroups($q)
+ {
+ $profile = $this->user->getProfile();
+ $groups = $profile->getGroups();
+
+ $q = strtolower($q);
+ while($groups->fetch()) {
+ # if $q is empty return everything
+ if(empty($q) ||
+ strpos(strtolower($gropus->nickname), $q)) {
+
+ $meta=array();
+
+ $meta['id'] = $groups->id; # saveKnownGroups needs ids
+ $meta['nickname'] = $groups->nickname;
+ $meta['name'] = $groups->fullname;
+ $meta['uri'] = $groups->uri;
+ $meta['avatar'] = $groups->mini_logo;
+
+ $this->list[] = clone($meta);
+ }
+ }
+ $groups->free();
+ }
+}
diff --git a/plugins/SocialLayout/SocialLayoutPlugin.php b/plugins/SocialLayout/SocialLayoutPlugin.php
new file mode 100644
index 000000000..cd5c08cfe
--- /dev/null
+++ b/plugins/SocialLayout/SocialLayoutPlugin.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Plugin for GNUSocial's design
+ *
+ * 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 GNUSocial
+ * @package StatusNet
+ * @author Shashi Gowda <connect2shashi@gmail.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://daisycha.in
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class SocialLayoutPlugin extends Plugin
+{
+ function onRouterInitialized($m)
+ {
+ return true;
+ }
+
+ function onEndShowStatusNetStyles($action)
+ {
+ $action->cssLink('plugins/SocialLayout/layout.css',
+ null, 'screen, projection, tv');
+ return true;
+ }
+
+ # show options to change the type of the notice being posted
+ function onEndShowNoticeFormData($form)
+ {
+ $form->out->elementStart('ul', 'social-switcher');
+ Event::handle('ShowSocialSwitcher', array($form));
+ $form->out->elementEnd('ul');
+ }
+
+ function onEndShowScripts($action)
+ {
+ $action->script('/plugins/SocialLayout/layout.js');
+ }
+}
diff --git a/plugins/SocialLayout/SocialObjectForm.php b/plugins/SocialLayout/SocialObjectForm.php
new file mode 100644
index 000000000..0633df698
--- /dev/null
+++ b/plugins/SocialLayout/SocialObjectForm.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for posting a social-object
+ *
+ * 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 Form
+ * @category GNUSocial
+ * @package StatusNet
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://daisycha.in
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/noticeform.php';
+
+class SocialObjectForm extends NoticeForm
+{
+ function id()
+ {
+ return 'form_'.$this->out->slug;
+ }
+
+ function formClass()
+ {
+ return 'form_notice form_'.$this->out->slug;
+ }
+
+ function action()
+ {
+ return common_local_url('new'.$this->out->slug);
+ }
+
+ function formLegend()
+ {
+ $this->out->element('legend', null, _('Send a notice'));
+ }
+
+ function formData()
+ {
+ if (Event::handle('StartShowNoticeFormData', array($this))) {
+
+ $this->showFormElements();
+ $this->showAttachmentElements();
+ $this->showReplyToElements();
+ $this->showReplyToElements();
+
+ Event::handle('EndShowNoticeFormData', array($this));
+ }
+ }
+
+ function formActions()
+ {
+ $this->out->element('input', array('id' => 'notice_action-submit',
+ 'class' => 'submit',
+ 'name' => 'status_submit',
+ 'type' => 'submit',
+ 'value' => _m('Send button for sending notice', 'Send')));
+ }
+
+ function showAttachmentElements()
+ {
+ if (common_config('attachments', 'uploads')) {
+ $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
+ $this->out->element('input', array('id' => 'notice_data-attach',
+ 'type' => 'file',
+ 'name' => 'attach',
+ 'title' => _('Attach a file')));
+ $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
+ }
+
+ }
+
+ function showReplyToElements()
+ {
+ if ($this->action) {
+ $this->out->hidden('notice_return-to', $this->action, 'returnto');
+ }
+ $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto');
+ }
+
+ function showLocationElements()
+ {
+ if ($this->user->shareLocation()) {
+ $this->out->hidden('notice_data-lat', empty($this->lat) ? (empty($this->profile->lat) ? null : $this->profile->lat) : $this->lat, 'lat');
+ $this->out->hidden('notice_data-lon', empty($this->lon) ? (empty($this->profile->lon) ? null : $this->profile->lon) : $this->lon, 'lon');
+ $this->out->hidden('notice_data-location_id', empty($this->location_id) ? (empty($this->profile->location_id) ? null : $this->profile->location_id) : $this->location_id, 'location_id');
+ $this->out->hidden('notice_data-location_ns', empty($this->location_ns) ? (empty($this->profile->location_ns) ? null : $this->profile->location_ns) : $this->location_ns, 'location_ns');
+
+ $this->out->elementStart('div', array('id' => 'notice_data-geo_wrap',
+ 'title' => common_local_url('geocode')));
+ $this->out->checkbox('notice_data-geo', _('Share my location'), true);
+ $this->out->elementEnd('div');
+ $this->out->inlineScript(' var NoticeDataGeo_text = {'.
+ 'ShareDisable: "'._('Do not share my location').'",'.
+ 'ErrorTimeout: "'._('Sorry, retrieving your geo location is taking longer than expected, please try again later').'"'.
+ '}');
+ }
+ }
+
+ # you'll mainly need to override this
+ function showFormElements()
+ {
+ $this->out->element('label', array('for' => 'notice_data-text'),
+ sprintf(_('What\'s up, %s?'), $this->user->nickname));
+ // XXX: vary by defined max size
+ $this->out->element('textarea', array('id' => 'notice_data-text',
+ 'cols' => 35,
+ 'rows' => 4,
+ 'name' => 'status_textarea'),
+ ($this->content) ? $this->content : '');
+
+ $contentLimit = Notice::maxContent();
+
+ if ($contentLimit > 0) {
+ $this->out->elementStart('dl', 'form_note');
+ $this->out->element('dt', null, _('Available characters'));
+ $this->out->element('dd', array('id' => 'notice_text-count'),
+ $contentLimit);
+ $this->out->elementEnd('dl');
+ }
+ }
+}
diff --git a/plugins/SocialLayout/layout.css b/plugins/SocialLayout/layout.css
new file mode 100644
index 000000000..9cee12d64
--- /dev/null
+++ b/plugins/SocialLayout/layout.css
@@ -0,0 +1,4 @@
+/*
+ * some common css for GNU Social controls
+ * example: The new notice form on crack.
+ */
diff --git a/plugins/SocialLayout/layout.js b/plugins/SocialLayout/layout.js
new file mode 100644
index 000000000..730ba6338
--- /dev/null
+++ b/plugins/SocialLayout/layout.js
@@ -0,0 +1,13 @@
+/*
+ * 1. Provide easy autocomplete functionality
+ * for social-object plugins to use in the
+ * notice form.
+ * They will need to specify the groups and
+ * mentions (@-replies) explicitly, since
+ * we won't be "parsing" their content for it
+ * autocomplete will help there.
+ * see: SocialAutocompleteAction.php
+ *
+ * 2. make some abstractions for social-objects
+ * use ajax.
+ */
diff --git a/plugins/SocialObject/NewSocialObjectAction.php b/plugins/SocialObject/NewSocialObjectAction.php
new file mode 100644
index 000000000..ad4e093d2
--- /dev/null
+++ b/plugins/SocialObject/NewSocialObjectAction.php
@@ -0,0 +1,210 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Base class for handling new social-objects
+ *
+ * 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 GNUSocial
+ * @package StatusNet
+ * @author Shashi Gowda <connect2shashi@gmail.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://daisycha.in
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class NewSocialObjectAction extends NewnoticeAction
+{
+ private $slug=null;
+ private $dbclass=null;
+
+ function saveNewNotice($metadata=array())
+ {
+ $this->validate();
+
+ # summarize social-object data to be put in as notice content
+ # should be usable in xmpp, email etc.
+ $content = $this->getSummary();
+ $replyto = $this->getReplyTo();
+
+ $metadata['replies'] = $this->getMentions();
+ $metadata['urls'] = $this->getUrls();
+ $metadata['groups'] = $this->getGroups();
+ $metadata['tags'] = $this->getTags();
+
+ # this is largely copied from parent::saveNewNotice
+
+ $user = common_current_user();
+ assert($user); // XXX: maybe an error instead...
+
+ #If an ID of 0 is wrongly passed here, it will cause a database error,
+ #so override it...
+ if ($replyto == 0) {
+ $replyto = 'false';
+ }
+
+ $upload = null;
+ $upload = MediaFile::fromUpload('attach');
+
+ if (isset($upload)) {
+
+ $content_shortened .= ' ' . $upload->shortUrl();
+
+ if (Notice::contentTooLong($content_shortened)) {
+ $upload->delete();
+ $this->clientError(
+ sprintf(
+ _('Max notice size is %d chars, including attachment URL.'),
+ Notice::maxContent()
+ )
+ );
+ }
+ }
+
+ $options = array('reply_to' => ($replyto == 'false') ? null : $replyto);
+
+ if ($user->shareLocation()) {
+ // use browser data if checked; otherwise profile data
+ if ($this->arg('notice_data-geo')) {
+ $locOptions = Notice::locationOptions($this->trimmed('lat'),
+ $this->trimmed('lon'),
+ $this->trimmed('location_id'),
+ $this->trimmed('location_ns'),
+ $user->getProfile());
+ } else {
+ $locOptions = Notice::locationOptions(null,
+ null,
+ null,
+ null,
+ $user->getProfile());
+ }
+
+ $options = array_merge($options, $locOptions, $metadata); ### $metadata too.
+ }
+
+ $notice = Notice::saveNew($user->id, $content_shortened, 'web', $options);
+ $this->saveNew($notice); ### <-- save me.
+
+ if (isset($upload)) {
+ $upload->attachToNotice($notice);
+ }
+
+ if ($this->boolean('ajax')) {
+ header('Content-Type: text/xml;charset=utf-8');
+ $this->xw->startDocument('1.0', 'UTF-8');
+ $this->elementStart('html');
+ $this->elementStart('head');
+ $this->element('title', null, _('Notice posted'));
+ $this->elementEnd('head');
+ $this->elementStart('body');
+ $this->showNotice($notice);
+ $this->elementEnd('body');
+ $this->elementEnd('html');
+ } else {
+ $returnto = $this->trimmed('returnto');
+
+ if ($returnto) {
+ $url = common_local_url($returnto,
+ array('nickname' => $user->nickname));
+ } else {
+ $url = common_local_url('shownotice',
+ array('notice' => $notice->id));
+ }
+ common_redirect($url, 303);
+ }
+ }
+
+ function validate()
+ {
+ return true;
+ }
+
+ function getSummary()
+ {
+ # have this function in the DB class,
+ # respect Notice::MaxContent()
+ }
+
+ function getReplyTo()
+ {
+ return $this->trimmed('inreplyto');
+ }
+
+ function getMentions()
+ {
+ }
+
+ function getUrls()
+ {
+ }
+
+ function getTags()
+ {
+ }
+
+ function getGroups()
+ {
+ }
+
+ function save()
+ {
+ # save your custom data, video, event or whatever.
+ }
+
+ function saveNew()
+ {
+ # save new social-object to its table.
+ }
+
+ function showPage()
+ {
+ if ($this->boolean('ajax')) {
+ header('Content-Type: text/xml;charset=utf-8');
+ $this->xw->startDocument('1.0', 'UTF-8');
+ $this->elementStart('html');
+ $this->elementStart('head');
+ $this->elementEnd('head');
+ $this->elementStart('body');
+ $this->showSocialObjectForm();
+ $this->elementEnd('body');
+ $this->elementEnd('html');
+ return false;
+ }
+ else {
+ parent::showPage();
+ }
+ }
+
+ function showNoticeForm()
+ {
+ $this->showSocialObjectForm();
+ }
+
+ # when user choses to post a social object,
+ # the existing notice box will be replaced
+ # with this.
+ # abstracted out in SocialLayout plugin
+ # has autocomplete for replies and groupnames
+ function showSocialObjectForm()
+ {
+ # show some awesome variation of SocialObjectForm
+ }
+}
diff --git a/plugins/SocialObject/SocialObject.php b/plugins/SocialObject/SocialObject.php
new file mode 100644
index 000000000..2d740c836
--- /dev/null
+++ b/plugins/SocialObject/SocialObject.php
@@ -0,0 +1,324 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+# Abstract class for social-objects
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class SocialObject extends Memcached_DataObject
+{
+ var $slug=null; # used in cache key
+
+ # save a new object
+ static function saveNew($args)
+ {
+ # save everything, add a
+ # Notice entry too.
+ # call Notice::saveNew with options
+ # like replies, groups and tags.
+ # and, cache blowing action.
+ }
+
+ function delete()
+ {
+ # delete me and my associated Notice
+ # more cache blowing action
+ parent::delete();
+ }
+
+ # make an atom entry
+ function asAtomEntry()
+ {
+ }
+
+ # make a json object
+ function asJson()
+ {
+ }
+
+ # return an array for Schema::ensureTable to define the table
+ static function tableDef()
+ {
+ return array();
+ }
+
+ # give out a stream, and cache it!
+ static function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0)
+ {
+ Notice::stream($fn, $args, $cachekey, $offset, $limit, $since_id, $max_id);
+ }
+
+ # try to fetch public stream from the cache if it is there
+ # or hit the DB, notice objects FIXME: make this return both
+ # notices and social-objects
+ function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0)
+ {
+ $ids = self::stream(array($this, '_publicStreamDirect'),
+ array(),
+ 'public:'.self::$slug,
+ $offset, $limit, $since_id, $max_id);
+ return self::getStreamByIds($ids);
+ }
+
+ # fetch public stream from the db and
+ # return the ids of the notices
+ # see also: Notice::_publicStreamDirect
+ function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0)
+ {
+ # make the code look nice :)
+ $table = $this->__table;
+ $id = $table.'.id';
+
+ $notice = new Notice();
+
+ $this->joinAdd();
+ $this->joinAdd($notice); # join notice table
+
+ $this->selectAdd(); // clears it
+ $this->selectAdd($id.' as id');
+
+ $this->orderBy($id.' DESC');
+
+ if (!is_null($offset)) {
+ $this->limit($offset, $limit);
+ }
+
+ if (common_config('public', 'localonly')) {
+ $this->whereAdd('notice.is_local = ' . Notice::LOCAL_PUBLIC);
+ } else {
+ # -1 == blacklisted, -2 == gateway (i.e. Twitter)
+ $this->whereAdd('notice.is_local !='. Notice::LOCAL_NONPUBLIC);
+ $this->whereAdd('notice.is_local !='. Notice::GATEWAY);
+ }
+
+ if ($since_id != 0) {
+ $this->whereAdd($id. ' > ' . $since_id);
+ }
+
+ if ($max_id != 0) {
+ $this->whereAdd($id. ' <= ' . $max_id);
+ }
+
+ $ids = array();
+
+ if ($this->find()) {
+ while ($this->fetch()) {
+ $ids[] = $this->id;
+ }
+ }
+
+ $this->free();
+ $this = NULL;
+
+ return $ids;
+ }
+
+ # get all social-objects by user
+ function userStream($user, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
+ {
+ // XXX: I'm not sure this is going to be any faster. It probably isn't.
+ $ids = self::stream(array($user, '_userSreamDirect'),
+ array($user),
+ 'profile:'.$this->slug.':notice_ids:' . $user->id,
+ $offset, $limit, $since_id, $max_id);
+
+ return self::getStreamByIds($ids);
+ }
+
+ # some scary, scary (hacked) upstream code follows.
+ function _userStreamDirect($user, $offset, $limit, $since_id, $max_id)
+ {
+ $table = $this->__table;
+ $id = $table.'.id';
+
+ $notice = new Notice();
+
+ // Temporary hack until notice_profile_id_idx is updated
+ // to (profile_id, id) instead of (profile_id, created, id).
+ // It's been falling back to PRIMARY instead, which is really
+ // very inefficient for a profile that hasn't posted in a few
+ // months. Even though forcing the index will cause a filesort,
+ // it's usually going to be better.
+ if (common_config('db', 'type') == 'mysql') {
+ $index = '';
+ $query = # join me
+ "select $id as id from notice join $table on ($id = notice.id) ".
+ "force index (notice_profile_id_idx) ".
+ "where notice.profile_id=" . $notice->escape($user->id);
+
+ if ($since_id != 0) {
+ $query .= " and $id > $since_id";
+ }
+
+ if ($max_id != 0) {
+ $query .= " and $id < $max_id";
+ }
+
+ $query .= " order by $id DESC";
+
+ if (!is_null($offset)) {
+ $query .= " LIMIT $limit OFFSET $offset";
+ }
+
+ $this->query($query);
+ } else {
+ $index = '';
+
+ $notice->profile_id = $user->id;
+
+ $this->joinAdd();
+ $this->joinAdd($notice);
+
+ $this->selectAdd();
+ $this->selectAdd("$id as id");
+
+ if ($since_id != 0) {
+ $this->whereAdd("$id > $since_id");
+ }
+
+ if ($max_id != 0) {
+ $this->whereAdd("$id <= $max_id");
+ }
+
+ $this->orderBy("$id DESC");
+
+ if (!is_null($offset)) {
+ $this->limit($offset, $limit);
+ }
+
+ $this->find();
+ }
+
+ $ids = array();
+
+ while ($this->fetch()) {
+ $ids[] = $this->id;
+ }
+
+ return $ids;
+ }
+
+ # get all social-objects in the group
+ function groupStream($group, $offset, $limit, $since_id=null, $max_id=null)
+ {
+ $ids = self::stream(array($this, '_groupStreamDirect'),
+ array($group),
+ 'user_group:'.$this->slug.':notice_ids:' . $group->id,
+ $offset, $limit, $since_id, $max_id);
+
+ return self::getStreamByIds($ids);
+ }
+
+ function _groupStreamDirect($group, $offset, $limit, $since_id, $max_id)
+ {
+ $table = $this->__table;
+
+ $inbox = new Group_inbox();
+
+ $inbox->group_id = $group->id;
+
+ $inbox->joinAdd();
+ $inbox->joinAdd($this);
+
+ $inbox->selectAdd();
+ $inbox->selectAdd('group_inbox.notice_id as id');
+
+ if ($since_id != 0) {
+ $inbox->whereAdd('group_inbox.notice_id > ' . $since_id);
+ }
+
+ if ($max_id != 0) {
+ $inbox->whereAdd('group_inbox.notice_id <= ' . $max_id);
+ }
+
+ $inbox->orderBy('group_inbox.notice_id DESC');
+
+ if (!is_null($offset)) {
+ $inbox->limit($offset, $limit);
+ }
+
+ $ids = array();
+
+ if ($inbox->find()) {
+ while ($inbox->fetch()) {
+ $ids[] = $inbox->notice_id;
+ }
+ }
+
+ return $ids;
+ }
+
+ # param: array of ids
+ # returns: array($notices, $social_objects)
+ # an example on how to use this:
+ #
+ # list($notices, $objects) = SocialObject::getStreamByIds($ids);
+ # while($notices->fetch()) {
+ # $obj = $objects[$notices->id];
+ # if(!empty($obj)) {
+ # # now use $notices and $obj,
+ # # they are associated.
+ # }
+ # }
+ #
+ static function getStreamByIds($ids, $classname=null) {
+ $notices = Notice::getSteamByIds($ids);
+ $objects = array();
+ if(empty($classname) && function_exists('get_called_class')) {
+ $classname = get_called_class(); # works in php 5.3 +
+ }
+
+ $cache = common_memcache();
+
+ # don't put array wrapper around $objects
+ # keep the id as key
+ if (!empty($cache)) {
+ $objects = array();
+ foreach ($ids as $id) {
+ $n = $classname::staticGet('id', $id);
+ if (!empty($n)) {
+ $objects[$id] = $n;
+ }
+ }
+ return array($notices, $objects);
+ } else {
+ $object = new $classname();
+ if (empty($ids)) {
+ return array($notices, array());
+ }
+ $objects->whereAdd('id in (' . implode(', ', $ids) . ')');
+
+ $objects->find();
+
+ $objects = array();
+
+ while ($object->fetch()) {
+ $objects[$notice->id] = clone($object);
+ }
+
+ # just return the $objects array, we can then use $notices->id
+ # each time to get the object associated
+ return new array($notices, $objects);
+ }
+ }
+}
diff --git a/plugins/SocialObject/SocialObjectPlugin.php b/plugins/SocialObject/SocialObjectPlugin.php
new file mode 100644
index 000000000..36cef0025
--- /dev/null
+++ b/plugins/SocialObject/SocialObjectPlugin.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * Base class for all social object plugins
+ *
+ * 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 GNUSocial
+ * @package StatusNet
+ * @author Shashi Gowda <connect2shashi@gmail.com>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://daisycha.in
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class SocialObjectPlugin extends Plugin
+{
+ # name to use in the UI
+ public $name=null;
+ # plural name for menus
+ private $name_plural=null;
+ # short name for use in urls
+ private $slug=null;
+ # plural slug
+ private $slug_plural=null;
+ # Database class
+ private $dbclass=null;
+
+ # setup DB tables etc.
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+ $classname = $this->dbclass
+ $schema->ensureTable($classname::tableDef());
+ return true;
+ }
+
+ # Add some urls to be routed to respective actions
+ function onRouterInitialized(&$m)
+ {
+ # timeline of all public social-objects
+ $m->connect('public/'.$this->slug_plural,
+ array('action' => 'public'.$this->slug_plural));
+ # timeline of this social-object for each user
+ $m->connect(':user/'.$this->slug_plural,
+ array('action' => 'user'.$this->slug_plural,
+ 'user' => '[a-zA-Z0-9]{1,64}'));
+ # timeline of this social-object for groups
+ $m->connect('group/:group/'.$this->slug_plural,
+ array('action' => 'group'.$this->slug_plural,
+ 'group' => '[a-zA-Z0-9]{1,64}'));
+ return true;
+ }
+
+ # use this to blow our caches.
+ # the alternative is to decide
+ # which caches to blow, no thanks
+ # Notice class already does that
+ function onEndCacheDelete($key)
+ {
+ $keys = array('public:notice_ids' => 'public:%s:notice_ids',
+ 'profile:notice_ids:' => 'profile:%s:notice_ids:',
+ 'user_group:notice_ids:' => 'user_group:%s:notice_ids:');
+ foreach($keys as $s => $r) {
+ $len = strlen($s);
+ # if key starts with $s,
+ if(substr($key, 0, $len) === $s) {
+ Memcached_DataObject::blow(sprintf($r, $this->slug)));
+ }
+ }
+ }
+
+ # Menu on public page
+ function onEndPublicGroupNav($action)
+ {
+ $action->out->menuItem(common_local_url('public'.$this->slug_plural), $this->name_plural);
+ return true;
+ }
+
+ # Menu on personal pages (/user, /user/all, /user/replies etc)
+ function onEndPersonalGroupNav($action)
+ {
+ $action->out->menuItem(common_local_url('user'.$this->slug_plural,
+ array('user' => $action->trimmed('nickname'))), $this->name_plural);
+ return true;
+ }
+
+ # Menu on group pages
+ function onEndGroupGroupNav($action)
+ {
+ $action->out->menuItem(common_local_url('user'.$this->slug_plural,
+ array('group' => $action->trimmed('nickname'))), $this->name_plural);
+ return true;
+ }
+
+ # This is the menu for cranking up the notice form
+ function onShowSocialSwitcher($form)
+ {
+ $form->out->elementStart('li', "social-switch {$this->slug}-switch");
+ $form->out->element('a',
+ array('href' => common_local_url('shownoticeform').'#social-'.$this->slug,
+ 'title' => $this->name)
+ , $this->name);
+ $form->out->elementEnd('ul');
+ }
+
+ # if the notice being shown has metadata for
+ # a social-object then render it differently
+ function onStartShowNoticeItem($widget)
+ {
+ if($this->isThisObject($widget->notice)) {
+
+ $this->showItem($widget);
+ $widget->showNoticeInfo();
+ $widget->showNoticeOptions();
+ # don't want to break other plugins
+ Event::handle('EndShowNoticeItem', array($widget));
+ # return false, i.e stop rendering the notice
+ return false;
+ }
+ # continue with showing notice
+ return true;
+ }
+
+ # this hook needs to be patched to upstream
+ function onStartSingleNoticeItem($widget)
+ {
+ return $this->showItem($widget);
+ }
+
+ # does the notice have metadata related to this social object?
+ function isThisObject($notice)
+ {
+ $obj=call_user_func($this->dbclass, 'staticGet',
+ array(array('id' => $notice->id)));
+ return !empty($obj);
+ }
+
+ # show object in timeline
+ function showItem($widget)
+ {
+ $classname = $this->slug.'Widget';
+ $widget=new $classname();
+ $widget->show();
+ }
+}
diff --git a/plugins/SocialObject/todo b/plugins/SocialObject/todo
new file mode 100644
index 000000000..1e04a546b
--- /dev/null
+++ b/plugins/SocialObject/todo
@@ -0,0 +1,8 @@
+* attr: name, model
+* ajaxFormUrl
+* asNotice
+* asAtom
+* asJson
+* ajaxForm
+* groups autocomplete
+* mentions autocomplete