Merge remote-tracking branch 'evan/blogplugin' into newblogplugin

This commit is contained in:
Evan Prodromou 2011-06-20 09:34:32 -04:00
commit 01996b1a46
7 changed files with 880 additions and 0 deletions

View File

@ -47,6 +47,7 @@ if (!defined('STATUSNET')) {
class UUID
{
const REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
protected $str = null;
/**

233
plugins/Blog/BlogEntry.php Normal file
View File

@ -0,0 +1,233 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Data structure for blog entries
*
* 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 Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Data structure for blog entries
*
* @category Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class BlogEntry extends Managed_DataObject
{
public $__table = 'blog_entry';
public $id; // UUID
public $profile_id; // int
public $title; // varchar(255)
public $summary; // text
public $content; // text
public $uri; // text
public $url; // text
public $created; // datetime
public $modified; // datetime
const TYPE = 'http://activitystrea.ms/schema/1.0/blog-entry';
static function staticGet($k, $v=null)
{
return Managed_DataObject::staticGet('blog_entry', $k, $v);
}
static function schemaDef()
{
return array(
'description' => 'lite blog entry',
'fields' => array(
'id' => array('type' => 'char',
'length' => 36,
'not null' => true,
'description' => 'Unique ID (UUID)'),
'profile_id' => array('type' => 'int',
'not null' => true,
'description' => 'Author profile ID'),
'title' => array('type' => 'varchar',
'length' => 255,
'description' => 'title of the entry'),
'summary' => array('type' => 'text',
'description' => 'initial summary'),
'content' => array('type' => 'text',
'description' => 'HTML content of the entry'),
'uri' => array('type' => 'varchar',
'length' => 255,
'description' => 'URI (probably http://) for this entry'),
'url' => array('type' => 'varchar',
'length' => 255,
'description' => 'URL (probably http://) for this entry'),
'created' => array('type' => 'datetime',
'not null' => true,
'description' => 'date this record was created'),
'modified' => array('type' => 'datetime',
'not null' => true,
'description' => 'date this record was created'),
),
'primary key' => array('id'),
'foreign keys' => array(
'blog_entry_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
),
'indexes' => array(
'blog_entry_created_idx' => array('created'),
'blog_entry_uri_idx' => array('uri'),
),
);
}
static function saveNew($profile, $title, $content, $options=null)
{
if (is_null($options)) {
$options = array();
}
$be = new BlogEntry();
$be->id = (string) new UUID();
$be->profile_id = $profile->id;
$be->title = htmlspecialchars($title);
$be->content = $content;
if (array_key_exists('summary', $options)) {
$be->summary = $options['summary'];
} else {
$be->summary = self::summarize($content);
}
$url = common_local_url('showblogentry', array('id' => $be->id));
if (!array_key_exists('uri', $options)) {
$options['uri'] = $url;
}
$be->uri = $options['uri'];
if (!array_key_exists('url', $options)) {
$options['url'] = $url;
}
$be->url = $options['url'];
if (!array_key_exists('created', $options)) {
$be->created = common_sql_now();
}
$be->created = $options['created'];
$be->modified = common_sql_now();
$be->insert();
// Use user's preferences for short URLs, if possible
try {
$user = $profile->getUser();
$shortUrl = File_redirection::makeShort($url,
empty($user) ? null : $user);
} catch (Exception $e) {
// Don't let this stop us.
$shortUrl = $url;
}
// XXX: this might be too long.
$options['rendered'] = $be->summary . ' ' .
XMLStringer::estring('a', array('href' => $shortUrl,
'class' => 'blog-entry'),
_('More...'));
$summaryText = html_entity_decode(strip_tags($summary), ENT_QUOTES, 'UTF-8');
if (Notice::contentTooLong($summaryText)) {
$summaryText = substr($summaryText, 0, Notice::maxContent() - mb_strlen($shortUrl) - 2) .
'… ' . $shortUrl;
}
$content = $summaryText;
// Override this no matter what.
$options['object_type'] = self::TYPE;
$source = array_key_exists('source', $options) ?
$options['source'] : 'web';
Notice::saveNew($profile->id, $content, $source, $options);
}
/**
* Summarize the contents of a blog post
*
* We take the first div or paragraph of the blog post if there's a hit;
* Otherwise we take the whole thing.
*
* @param string $html HTML of full content
*/
static function summarize($html)
{
if (preg_match('#<p>.*?</p>#s', $html, $matches)) {
return $matches[0];
} else if (preg_match('#<div>.*?</div>#s', $html, $matches)) {
return $matches[0];
} else {
return $html;
}
}
static function fromNotice($notice)
{
return BlogEntry::staticGet('uri', $notice->uri);
}
function getNotice()
{
return Notice::staticGet('uri', $this->uri);
}
function asActivityObject()
{
$obj = new ActivityObject();
$obj->id = $this->uri;
$obj->type = self::TYPE;
$obj->title = $this->title;
$obj->summary = $this->summary;
$obj->content = $this->content;
$obj->link = $this->url;
return $obj;
}
}

210
plugins/Blog/BlogPlugin.php Normal file
View File

@ -0,0 +1,210 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* A microapp to implement lite blogging
*
* 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 Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Blog plugin
*
* Many social systems have a way to write and share long-form texts with
* your network. This microapp plugin lets users post blog entries.
*
* @category Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class BlogPlugin extends MicroAppPlugin
{
/**
* Database schema setup
*
* @see Schema
* @see ColumnDef
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onCheckSchema()
{
$schema = Schema::get();
$schema->ensureTable('blog_entry', BlogEntry::schemaDef());
return true;
}
/**
* Load related modules when needed
*
* @param string $cls Name of the class to be loaded
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onAutoload($cls)
{
$dir = dirname(__FILE__);
switch ($cls)
{
case 'NewblogentryAction':
case 'ShowblogentryAction':
include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'BlogEntryForm':
case 'BlogEntryListItem':
include_once $dir . '/'.strtolower($cls).'.php';
return false;
case 'BlogEntry':
include_once $dir . '/'.$cls.'.php';
return false;
default:
return true;
}
}
/**
* Map URLs to actions
*
* @param Net_URL_Mapper $m path-to-action mapper
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onRouterInitialized($m)
{
$m->connect('blog/new',
array('action' => 'newblogentry'));
$m->connect('blog/:id',
array('action' => 'showblogentry'),
array('id' => UUID::REGEX));
return true;
}
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'Blog',
'version' => STATUSNET_VERSION,
'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:Blog',
'rawdescription' =>
_m('Let users write and share long-form texts.'));
return true;
}
function appTitle()
{
return _m('Blog');
}
function tag()
{
return 'blog';
}
function types()
{
return array(BlogEntry::TYPE);
}
function saveNoticeFromActivity($activity, $actor, $options=array())
{
if (count($activity->objects) != 1) {
// TRANS: Exception thrown when there are too many activity objects.
throw new ClientException(_m('Too many activity objects.'));
}
$entryObj = $activity->objects[0];
if ($entryObj->type != BlogEntry::TYPE) {
// TRANS: Exception thrown when blog plugin comes across a non-event type object.
throw new ClientException(_m('Wrong type for object.'));
}
$notice = null;
switch ($activity->verb) {
case ActivityVerb::POST:
$notice = BlogEntry::saveNew($actor,
$entryObj->title,
$entryObj->content,
$options);
break;
default:
// TRANS: Exception thrown when blog plugin comes across a undefined verb.
throw new ClientException(_m('Unknown verb for blog entries.'));
}
return $notice;
}
function activityObjectFromNotice($notice)
{
$entry = BlogEntry::fromNotice($notice);
if (empty($entry)) {
throw new ClientException(sprintf(_('No blog entry for notice %s'),
$notice->id));
}
return $entry->asActivityObject();
}
function entryForm($out)
{
return new BlogEntryForm($out);
}
function deleteRelated($notice)
{
if ($notice->object_type == BlogEntry::TYPE) {
$entry = BlogEntry::fromNotice($notice);
if (exists($entry)) {
$entry->delete();
}
}
}
function adaptNoticeListItem($nli)
{
$notice = $nli->notice;
if ($notice->object_type == BlogEntry::TYPE) {
return new BlogEntryListItem($nli);
}
return null;
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Form for creating a blog entry
*
* 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 Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Form for creating a blog entry
*
* @category Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class BlogEntryForm extends Form
{
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
return 'form_new_blog_entry';
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings ajax-notice';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('newblogentry');
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->elementStart('fieldset', array('id' => 'new_blog_entry_data'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->out->input('blog-entry-title',
// TRANS: Field label on blog entry form.
_m('LABEL','Title'),
null,
// TRANS: Field title on blog entry form.
_m('Title of the blog entry.'),
'title');
$this->unli();
$this->li();
$this->out->textarea('blog-entry-content',
// TRANS: Field label on event form.
_m('LABEL','Text'),
null,
// TRANS: Field title on event form.
_m('Text of the blog entry.'),
'content');
$this->unli();
$this->out->elementEnd('ul');
$toWidget = new ToSelector($this->out,
common_current_user(),
null);
$toWidget->show();
$this->out->elementEnd('fieldset');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
// TRANS: Button text to save an event..
$this->out->submit('blog-entry-submit',
_m('BUTTON', 'Save'),
'submit',
'submit');
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* NoticeListItem adapter for blog entries
*
* 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 Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* NoticeListItem adapter for blog entries
*
* @category General
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class BlogEntryListItem extends NoticeListItemAdapter
{
function showNotice()
{
$this->out->elementStart('div', 'entry-title');
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
}
function showContent()
{
$notice = $this->nli->notice;
$out = $this->nli->out;
$entry = BlogEntry::fromNotice($notice);
if (empty($entry)) {
throw new Exception('BlogEntryListItem used for non-blog notice.');
}
$out->elementStart('h4', array('class' => 'blog-entry-title'));
$out->element('a', array('href' => $notice->bestUrl()), $entry->title);
$out->elementEnd('h4');
$out->element('div', 'blog-entry-summary', $entry->summary);
// XXX: hide content initially; click More... for full text.
$out->element('div', 'blog-entry-content', $entry->content);
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Save a new blog entry
*
* 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 Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Save a new blog entry
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class NewblogentryAction extends Action
{
protected $user;
protected $title;
protected $content;
/**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
if (!$this->isPost()) {
throw new ClientException(_('Must be a POST.'), 405);
}
$this->user = common_current_user();
if (empty($this->user)) {
// TRANS: Client exception thrown when trying to post a blog entry while not logged in.
throw new ClientException(_m('Must be logged in to post a blog entry.'),
403);
}
$this->checkSessionToken();
$this->title = $this->trimmed('title');
if (empty($this->title)) {
// TRANS: Client exception thrown when trying to post a blog entry without providing a title.
throw new ClientException(_m('Title required.'));
}
$this->content = $this->trimmed('content');
if (empty($this->content)) {
// TRANS: Client exception thrown when trying to post a blog entry without providing content.
throw new ClientException(_m('Content required.'));
}
return true;
}
/**
* Handler method
*
* @param array $argarray is ignored since it's now passed in in prepare()
*
* @return void
*/
function handle($argarray=null)
{
$options = array();
// Does the heavy-lifting for getting "To:" information
ToSelector::fillOptions($this, $options);
$options['source'] = 'web';
$profile = $this->user->getProfile();
$saved = BlogEntry::saveNew($profile,
$this->title,
$this->content,
$options);
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');
// TRANS: Page title after sending a notice.
$this->element('title', null, _m('Blog entry saved'));
$this->elementEnd('head');
$this->elementStart('body');
$this->showNotice($saved);
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect($saved->bestUrl(), 303);
}
}
}

View File

@ -0,0 +1,86 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Show a blog entry
*
* 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 Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Show a blog entry
*
* @category Blog
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class ShowblogentryAction extends ShownoticeAction
{
protected $id;
protected $entry;
function getNotice()
{
$this->id = $this->trimmed('id');
$this->entry = BlogEntry::staticGet('id', $this->id);
if (empty($this->entry)) {
// TRANS: Client exception thrown when referring to a non-existing blog entry.
throw new ClientException(_m('No such entry.'), 404);
}
$notice = $this->entry->getNotice();
if (empty($notice)) {
// TRANS: Client exception thrown when referring to a non-existing blog entry.
throw new ClientException(_m('No such entry.'), 404);
}
return $notice;
}
/**
* Title of the page
*
* Used by Action class for layout.
*
* @return string page tile
*/
function title()
{
// XXX: check for double-encoding
return (empty($this->entry->title)) ? _m('Untitled') : $this->entry->title;
}
}