From e7dc786b12638b0c7e4f3ba54308daa3adc4e8a6 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Wed, 9 Jun 2010 23:44:32 -0400 Subject: 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 --- README.branch | 14 + plugins/SocialLayout/SocialAutocompleteAction.php | 127 +++++++++ plugins/SocialLayout/SocialLayoutPlugin.php | 60 ++++ plugins/SocialLayout/SocialObjectForm.php | 141 ++++++++++ plugins/SocialLayout/layout.css | 4 + plugins/SocialLayout/layout.js | 13 + plugins/SocialObject/NewSocialObjectAction.php | 210 ++++++++++++++ plugins/SocialObject/SocialObject.php | 324 ++++++++++++++++++++++ plugins/SocialObject/SocialObjectPlugin.php | 164 +++++++++++ plugins/SocialObject/todo | 8 + 10 files changed, 1065 insertions(+) create mode 100644 README.branch create mode 100644 plugins/SocialLayout/SocialAutocompleteAction.php create mode 100644 plugins/SocialLayout/SocialLayoutPlugin.php create mode 100644 plugins/SocialLayout/SocialObjectForm.php create mode 100644 plugins/SocialLayout/layout.css create mode 100644 plugins/SocialLayout/layout.js create mode 100644 plugins/SocialObject/NewSocialObjectAction.php create mode 100644 plugins/SocialObject/SocialObject.php create mode 100644 plugins/SocialObject/SocialObjectPlugin.php create mode 100644 plugins/SocialObject/todo 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 @@ +. + * + * @category GNUSocial + * @package StatusNet + * @author Shashi Gowda + * @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 @@ +. + * + * @category GNUSocial + * @package StatusNet + * @author Shashi Gowda + * @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 @@ +. + * + * @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 @@ +. + * + * @category GNUSocial + * @package StatusNet + * @author Shashi Gowda + * @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 @@ +. + */ + +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 @@ +. + * + * @category GNUSocial + * @package StatusNet + * @author Shashi Gowda + * @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 -- cgit v1.2.3