Merge branch 'master' of git@gitorious.org:statusnet/mainline

This commit is contained in:
Evan Prodromou 2009-12-01 12:11:05 -05:00
commit 42da45d3bc
150 changed files with 25141 additions and 14390 deletions

View File

@ -89,12 +89,16 @@ class FinishremotesubscribeAction extends Action
}
$remote = Remote_profile::staticGet('uri', $service->getListenerURI());
if ($remote) {
// Note remote profile may not have been saved yet.
// @fixme not convinced this is correct at all!
$profile = Profile::staticGet($remote->id);
$profile = Profile::staticGet($remote->id);
if ($user->hasBlocked($profile)) {
$this->clientError(_('That user has blocked you from subscribing.'));
return;
if ($user->hasBlocked($profile)) {
$this->clientError(_('That user has blocked you from subscribing.'));
return;
}
}
/* Perform the handling itself via libomb. */
@ -122,6 +126,7 @@ class FinishremotesubscribeAction extends Action
/* The service URLs are not accessible from datastore, so setting them
after insertion of the profile. */
$remote = Remote_profile::staticGet('uri', $service->getListenerURI());
$orig_remote = clone($remote);
$remote->postnoticeurl =

View File

@ -53,6 +53,13 @@ class NoticesearchrssAction extends Rss10Action
{
return true;
}
function prepare($args)
{
parent::prepare($args);
$this->notices = $this->getNotices();
return true;
}
function getNotices($limit=0)
{

View File

@ -81,7 +81,7 @@ class Avatar extends Memcached_DataObject
if (empty($server)) {
$server = common_config('site', 'server');
}
common_debug('path = ' . $path);
// XXX: protocol
return 'http://'.$server.$path.$filename;

View File

@ -922,13 +922,14 @@ class Notice extends Memcached_DataObject
}
$groups = $this->saveGroups();
$profile = $this->getProfile();
foreach ($groups as $group) {
$users = $group->getUserMembers();
foreach ($users as $id) {
if (!array_key_exists($id, $ni)) {
$user = User::staticGet('id', $id);
if (!$user->hasBlocked($notice->profile_id)) {
if (!$user->hasBlocked($profile)) {
$ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
}
}
@ -964,7 +965,10 @@ class Notice extends Memcached_DataObject
}
if ($cnt >= MAX_BOXCARS) {
$inbox = new Notice_inbox();
$inbox->query($qry);
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
$qry = $qryhdr;
$cnt = 0;
}
@ -972,7 +976,10 @@ class Notice extends Memcached_DataObject
if ($cnt > 0) {
$inbox = new Notice_inbox();
$inbox->query($qry);
$result = $inbox->query($qry);
if (PEAR::isError($result)) {
common_log_db_error($inbox, $qry);
}
}
return;

View File

@ -1,195 +0,0 @@
<?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') && !defined('LACONICA')) { exit(1); }
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
abstract class Plugin_DataObject extends Memcached_DataObject
{
function table() {
static $table = null;
if($table == null) {
$table = array();
$DB = $this->getDatabaseConnection();
$dbtype = $DB->phptype;
$tableDef = $this->tableDef();
foreach($tableDef->columns as $columnDef){
switch(strtoupper($columnDef->type)) {
/*shamelessly copied from DB_DataObject_Generator*/
case 'INT':
case 'INT2': // postgres
case 'INT4': // postgres
case 'INT8': // postgres
case 'SERIAL4': // postgres
case 'SERIAL8': // postgres
case 'INTEGER':
case 'TINYINT':
case 'SMALLINT':
case 'MEDIUMINT':
case 'BIGINT':
$type = DB_DATAOBJECT_INT;
if ($columnDef->size == 1) {
$type += DB_DATAOBJECT_BOOL;
}
break;
case 'REAL':
case 'DOUBLE':
case 'DOUBLE PRECISION': // double precision (firebird)
case 'FLOAT':
case 'FLOAT4': // real (postgres)
case 'FLOAT8': // double precision (postgres)
case 'DECIMAL':
case 'MONEY': // mssql and maybe others
case 'NUMERIC':
case 'NUMBER': // oci8
$type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
break;
case 'YEAR':
$type = DB_DATAOBJECT_INT;
break;
case 'BIT':
case 'BOOL':
case 'BOOLEAN':
$type = DB_DATAOBJECT_BOOL;
// postgres needs to quote '0'
if ($dbtype == 'pgsql') {
$type += DB_DATAOBJECT_STR;
}
break;
case 'STRING':
case 'CHAR':
case 'VARCHAR':
case 'VARCHAR2':
case 'TINYTEXT':
case 'ENUM':
case 'SET': // not really but oh well
case 'POINT': // mysql geometry stuff - not really string - but will do..
case 'TIMESTAMPTZ': // postgres
case 'BPCHAR': // postgres
case 'INTERVAL': // postgres (eg. '12 days')
case 'CIDR': // postgres IP net spec
case 'INET': // postgres IP
case 'MACADDR': // postgress network Mac address.
case 'INTEGER[]': // postgres type
case 'BOOLEAN[]': // postgres type
$type = DB_DATAOBJECT_STR;
break;
case 'TEXT':
case 'MEDIUMTEXT':
case 'LONGTEXT':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
break;
case 'DATE':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
break;
case 'TIME':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
break;
case 'DATETIME':
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
break;
case 'TIMESTAMP': // do other databases use this???
$type = ($dbtype == 'mysql') ?
DB_DATAOBJECT_MYSQLTIMESTAMP :
DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
break;
case 'BLOB': /// these should really be ignored!!!???
case 'TINYBLOB':
case 'MEDIUMBLOB':
case 'LONGBLOB':
case 'CLOB': // oracle character lob support
case 'BYTEA': // postgres blob support..
$type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
break;
default:
throw new Exception("Cannot handle datatype: $columnDef->type");
}
if(! $columnDef->nullable) {
$type+=DB_DATAOBJECT_NOTNULL;
}
$table[$columnDef->name]=$type;
}
}
return $table;
}
function keys() {
static $keys = null;
if($keys == null) {
$keys = array();
$tableDef = $this->tableDef();
foreach($tableDef->columns as $columnDef){
if($columnDef->key != null){
$keys[] = $columnDef->name;
}
}
}
return $keys;
}
function sequenceKey() {
static $sequenceKey = null;
if($sequenceKey == null) {
$sequenceKey = array(false,false);
$tableDef = $this->tableDef();
foreach($tableDef->columns as $columnDef){
if($columnDef->key == 'PRI' && $columnDef->auto_increment){
$sequenceKey=array($columnDef->name,true);
}
}
}
return $sequenceKey;
}
/**
* Get the TableDef object that represents the table backing this class
* Ideally, this function would a static function, but PHP doesn't allow
* abstract static functions
* @return TableDef TableDef instance
*/
abstract function tableDef();
}

View File

@ -43,4 +43,14 @@ class Remote_profile extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function hasRight($right)
{
$profile = Profile::staticGet($this->id);
if ($profile) {
return $profile->hasright($right);
} else {
throw new Exception("Missing profile");
}
}
}

View File

@ -1,4 +1,3 @@
[avatar]
profile_id = 129
original = 17
@ -542,4 +541,25 @@ created = 142
modified = 384
[user_group__keys]
id = N
id = N
[user_openid]
canonical = 130
display = 130
user_id = 129
created = 142
modified = 384
[user_openid__keys]
canonical = K
display = U
[user_openid_trustroot]
trustroot = 130
user_id = 129
created = 142
modified = 384
[user_openid__keys]
trustroot = K
user_id = K

View File

@ -22,9 +22,6 @@ consumer_key = consumer:consumer_key
[nonce]
consumer_key,token = token:consumer_key,token
[user_openid]
user_id = user:id
[confirm_address]
user_id = user:id

View File

@ -36,7 +36,7 @@ and many features people expect from microblogging sites are not yet implemented
* [Facebook](http://www.facebook.com/) integration
* Image, video, audio notices
There is [a list of bugs and features](http://status.net/trac/) that you may find
There is [a list of bugs and features](http://status.net/bugs/) that you may find
interesting. New ideas or complaints are very welcome.

View File

@ -1142,15 +1142,10 @@ class ApiAction extends Action
function getTargetUser($id)
{
if (empty($id)) {
if (!preg_match('/^[a-zA-Z0-9]+$/', $id)) {
// Twitter supports these other ways of passing the user ID
if (is_numeric($this->arg('id'))) {
return User::staticGet($this->arg('id'));
} else if ($this->arg('id')) {
$nickname = common_canonical_nickname($this->arg('id'));
return User::staticGet('nickname', $nickname);
} else if ($this->arg('user_id')) {
if ($this->arg('user_id')) {
// This is to ensure that a non-numeric user_id still
// overrides screen_name even if it doesn't get used
if (is_numeric($this->arg('user_id'))) {
@ -1159,6 +1154,12 @@ class ApiAction extends Action
} else if ($this->arg('screen_name')) {
$nickname = common_canonical_nickname($this->arg('screen_name'));
return User::staticGet('nickname', $nickname);
} else if (is_numeric($this->arg('id'))) {
return User::staticGet($this->arg('id'));
} else if ($this->arg('id')) {
$nickname = common_canonical_nickname($this->arg('id'));
return User::staticGet('nickname', $nickname);
} else {
// Fall back to trying the currently authenticated user
return $this->auth_user;

169
lib/columndef.php Normal file
View File

@ -0,0 +1,169 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Database schema utilities
*
* 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 Database
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* A class encapsulating the structure of a column in a table.
*
* @category Database
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ColumnDef
{
/** name of the column. */
public $name;
/** type of column, e.g. 'int', 'varchar' */
public $type;
/** size of the column. */
public $size;
/** boolean flag; can it be null? */
public $nullable;
/**
* type of key: null = no key; 'PRI' => primary;
* 'UNI' => unique key; 'MUL' => multiple values.
*/
public $key;
/** default value if any. */
public $default;
/** 'extra' stuff. Returned by MySQL, largely
* unused. */
public $extra;
/** auto increment this field if no value is specific for it during an insert **/
public $auto_increment;
/**
* Constructor.
*
* @param string $name name of the column
* @param string $type type of the column
* @param int $size size of the column
* @param boolean $nullable can this be null?
* @param string $key type of key
* @param value $default default value
* @param value $extra unused
*/
function __construct($name=null, $type=null, $size=null,
$nullable=true, $key=null, $default=null,
$extra=null, $auto_increment=false)
{
$this->name = strtolower($name);
$this->type = strtolower($type);
$this->size = $size+0;
$this->nullable = $nullable;
$this->key = $key;
$this->default = $default;
$this->extra = $extra;
$this->auto_increment = $auto_increment;
}
/**
* Compares this columndef with another to see
* if they're functionally equivalent.
*
* @param ColumnDef $other column to compare
*
* @return boolean true if equivalent, otherwise false.
*/
function equals($other)
{
return ($this->name == $other->name &&
$this->_typeMatch($other) &&
$this->_defaultMatch($other) &&
$this->_nullMatch($other) &&
$this->key == $other->key &&
$this->auto_increment == $other->auto_increment);
}
/**
* Does the type of this column match the
* type of the other column?
*
* Checks the type and size of a column. Tries
* to ignore differences between synonymous
* data types, like 'integer' and 'int'.
*
* @param ColumnDef $other other column to check
*
* @return boolean true if they're about equivalent
*/
private function _typeMatch($other)
{
switch ($this->type) {
case 'integer':
case 'int':
return ($other->type == 'integer' ||
$other->type == 'int');
break;
default:
return ($this->type == $other->type &&
$this->size == $other->size);
}
}
/**
* Does the default behaviour of this column match
* the other?
*
* @param ColumnDef $other other column to check
*
* @return boolean true if defaults are effectively the same.
*/
private function _defaultMatch($other)
{
return ((is_null($this->default) && is_null($other->default)) ||
($this->default == $other->default));
}
/**
* Does the null behaviour of this column match
* the other?
*
* @param ColumnDef $other other column to check
*
* @return boolean true if these columns 'null' the same.
*/
private function _nullMatch($other)
{
return ((!is_null($this->default) && !is_null($other->default) &&
$this->default == $other->default) ||
($this->nullable == $other->nullable));
}
}

View File

@ -34,9 +34,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
require_once INSTALLDIR.'/lib/xmloutputter.php';
define('PAGE_TYPE_PREFS',
'text/html,application/xhtml+xml,'.
'application/xml;q=0.3,text/xml;q=0.2');
// Can include XHTML options but these are too fragile in practice.
define('PAGE_TYPE_PREFS', 'text/html');
/**
* Low-level generator for HTML

View File

@ -197,6 +197,7 @@ class OAuthClient
'timeout' => 120,
'follow_redirects' => true,
'ssl_verify_peer' => false,
'ssl_verify_host' => false
));
// Twitter is strict about accepting invalid "Expect" headers

View File

@ -463,6 +463,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore
$subscriber = $this->_getAnyProfile($subscriber_uri);
if (!$subscriber->hasRight(Right::SUBSCRIBE)) {
common_log(LOG_INFO, __METHOD__ . ": remote subscriber banned ($subscriber_uri subbing to $subscribed_user_uri)");
return _('You have been banned from subscribing.');
}

View File

@ -67,7 +67,14 @@ function omb_hmac_sha1()
function omb_broadcast_notice($notice)
{
$omb_notice = notice_to_omb_notice($notice);
try {
$omb_notice = notice_to_omb_notice($notice);
} catch (Exception $e) {
// @fixme we should clean up or highlight the problem item
common_log(LOG_ERR, 'Invalid OMB outgoing notice for notice ' . $notice->id);
common_log(LOG_ERR, 'Error status '.$e);
return true;
}
/* Get remote users subscribed to this profile. */
$rp = new Remote_profile();
@ -102,7 +109,7 @@ function omb_broadcast_notice($notice)
common_debug('Finished to ' . $rp->postnoticeurl, __FILE__);
}
return;
return true;
}
function omb_broadcast_profile($profile)

View File

@ -372,26 +372,6 @@ class Schema
return true;
}
/**
* Ensures that the table that backs a given
* Plugin_DataObject class exists.
*
* If the table does not yet exist, it will
* create the table. If it does exist, it will
* alter the table to match the column definitions.
*
* @param Plugin_DataObject $dataObjectClass
*
* @return boolean success flag
*/
public function ensureDataObject($dataObjectClass)
{
$obj = new $dataObjectClass();
$tableDef = $obj->tableDef();
return $this->ensureTable($tableDef->name,$tableDef->columns);
}
/**
* Ensures that a table exists with the given
* name and the given column definitions.
@ -547,171 +527,3 @@ class Schema
return $sql;
}
}
/**
* A class encapsulating the structure of a table.
*
* @category Database
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class TableDef
{
/** name of the table */
public $name;
/** array of ColumnDef objects for the columns. */
public $columns;
/**
* Constructor.
*
* @param string $name name of the table
* @param array $columns columns in the table
*/
function __construct($name=null,$columns=null)
{
$this->name = $name;
$this->columns = $columns;
}
}
/**
* A class encapsulating the structure of a column in a table.
*
* @category Database
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class ColumnDef
{
/** name of the column. */
public $name;
/** type of column, e.g. 'int', 'varchar' */
public $type;
/** size of the column. */
public $size;
/** boolean flag; can it be null? */
public $nullable;
/**
* type of key: null = no key; 'PRI' => primary;
* 'UNI' => unique key; 'MUL' => multiple values.
*/
public $key;
/** default value if any. */
public $default;
/** 'extra' stuff. Returned by MySQL, largely
* unused. */
public $extra;
/** auto increment this field if no value is specific for it during an insert **/
public $auto_increment;
/**
* Constructor.
*
* @param string $name name of the column
* @param string $type type of the column
* @param int $size size of the column
* @param boolean $nullable can this be null?
* @param string $key type of key
* @param value $default default value
* @param value $extra unused
*/
function __construct($name=null, $type=null, $size=null,
$nullable=true, $key=null, $default=null,
$extra=null, $auto_increment=false)
{
$this->name = strtolower($name);
$this->type = strtolower($type);
$this->size = $size+0;
$this->nullable = $nullable;
$this->key = $key;
$this->default = $default;
$this->extra = $extra;
$this->auto_increment = $auto_increment;
}
/**
* Compares this columndef with another to see
* if they're functionally equivalent.
*
* @param ColumnDef $other column to compare
*
* @return boolean true if equivalent, otherwise false.
*/
function equals($other)
{
return ($this->name == $other->name &&
$this->_typeMatch($other) &&
$this->_defaultMatch($other) &&
$this->_nullMatch($other) &&
$this->key == $other->key &&
$this->auto_increment == $other->auto_increment);
}
/**
* Does the type of this column match the
* type of the other column?
*
* Checks the type and size of a column. Tries
* to ignore differences between synonymous
* data types, like 'integer' and 'int'.
*
* @param ColumnDef $other other column to check
*
* @return boolean true if they're about equivalent
*/
private function _typeMatch($other)
{
switch ($this->type) {
case 'integer':
case 'int':
return ($other->type == 'integer' ||
$other->type == 'int');
break;
default:
return ($this->type == $other->type &&
$this->size == $other->size);
}
}
/**
* Does the default behaviour of this column match
* the other?
*
* @param ColumnDef $other other column to check
*
* @return boolean true if defaults are effectively the same.
*/
private function _defaultMatch($other)
{
return ((is_null($this->default) && is_null($other->default)) ||
($this->default == $other->default));
}
/**
* Does the null behaviour of this column match
* the other?
*
* @param ColumnDef $other other column to check
*
* @return boolean true if these columns 'null' the same.
*/
private function _nullMatch($other)
{
return ((!is_null($this->default) && !is_null($other->default) &&
$this->default == $other->default) ||
($this->nullable == $other->nullable));
}
}

63
lib/tabledef.php Normal file
View File

@ -0,0 +1,63 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Database schema utilities
*
* 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 Database
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* A class encapsulating the structure of a table.
*
* @category Database
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class TableDef
{
/** name of the table */
public $name;
/** array of ColumnDef objects for the columns. */
public $columns;
/**
* Constructor.
*
* @param string $name name of the table
* @param array $columns columns in the table
*/
function __construct($name=null,$columns=null)
{
$this->name = $name;
$this->columns = $columns;
}
}

View File

@ -58,18 +58,17 @@ function common_init_language()
// (say, ga_ES for Galego/Galician) it seems to take it.
common_init_locale("en_US");
// Note that this setlocale() call may "fail" but this is harmless;
// gettext will still select the right language.
$language = common_language();
$locale_set = common_init_locale($language);
setlocale(LC_CTYPE, 'C');
// So we do not have to make people install the gettext locales
$path = common_config('site','locale_path');
bindtextdomain("statusnet", $path);
bind_textdomain_codeset("statusnet", "UTF-8");
textdomain("statusnet");
if(!$locale_set) {
common_log(LOG_INFO, 'Language requested:' . $language . ' - locale could not be set. Perhaps that system locale is not installed.', __FILE__);
}
}
function common_timezone()
@ -1051,8 +1050,27 @@ function common_log_line($priority, $msg)
return date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
}
function common_request_id()
{
$pid = getmypid();
if (php_sapi_name() == 'cli') {
return $pid;
} else {
static $req_id = null;
if (!isset($req_id)) {
$req_id = substr(md5(mt_rand()), 0, 8);
}
if (isset($_SERVER['REQUEST_URI'])) {
$url = $_SERVER['REQUEST_URI'];
}
$method = $_SERVER['REQUEST_METHOD'];
return "$pid.$req_id $method $url";
}
}
function common_log($priority, $msg, $filename=null)
{
$msg = '[' . common_request_id() . '] ' . $msg;
$logfile = common_config('site', 'logfile');
if ($logfile) {
$log = fopen($logfile, "a");

11
locale/Makefile Normal file
View File

@ -0,0 +1,11 @@
# Warning: do not transform tabs to spaces in this file.
all : translations
translations : */LC_MESSAGES/statusnet.mo
clean :
rm -f */LC_MESSAGES/statusnet.mo
%.mo : %.po
msgfmt -o $@ $<

9
locale/README Normal file
View File

@ -0,0 +1,9 @@
Localizations for StatusNet are being maintained through TranslateWiki:
http://translatewiki.net/wiki/Translating:StatusNet
Note if you are working with a direct git checkout, you will need to build
the binary .mo files from the .po source files for translations to work
in the web app.
If gettext and GNU make are installed, you can simply run 'make' in this
directory to build them.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
/**
* StatusNet, the distributed open-source microblogging tool
*
* Superclass for plugins that do authentication
* Superclass for plugins that do authentication and/or authorization
*
* PHP version 5
*
@ -204,7 +204,16 @@ abstract class AuthenticationPlugin extends Plugin
function onCheckSchema() {
$schema = Schema::get();
$schema->ensureDataObject('User_username');
$schema->ensureTable('user_username',
array(new ColumnDef('provider_name', 'varchar',
'255', false, 'PRI'),
new ColumnDef('username', 'varchar',
'255', false, 'PRI'),
new ColumnDef('user_id', 'integer',
null, false),
new ColumnDef('created', 'datetime',
null, false),
new ColumnDef('modified', 'timestamp')));
return true;
}

View File

@ -2,9 +2,9 @@
/**
* Table Definition for user_username
*/
require_once INSTALLDIR.'/classes/Plugin_DataObject.php';
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class User_username extends Plugin_DataObject
class User_username extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
@ -43,22 +43,4 @@ class User_username extends Plugin_DataObject
return false;
}
}
/**
* Get the TableDef object that represents the table backing this class
* @return TableDef TableDef instance
*/
function tableDef()
{
return new TableDef($this->__table,
array(new ColumnDef('provider_name', 'varchar',
'255', false, 'PRI'),
new ColumnDef('username', 'varchar',
'255', false, 'PRI'),
new ColumnDef('user_id', 'integer',
null, false),
new ColumnDef('created', 'datetime',
null, false),
new ColumnDef('modified', 'timestamp')));
}
}

View File

@ -1,114 +0,0 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Menu for login group of actions
*
* 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 Menu
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/widget.php';
/**
* Menu for login group of actions
*
* @category Output
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see Widget
*/
class FBCLoginGroupNav extends Widget
{
var $action = null;
/**
* Construction
*
* @param Action $action current action, used for output
*/
function __construct($action=null)
{
parent::__construct($action);
$this->action = $action;
}
/**
* Show the menu
*
* @return void
*/
function show()
{
$this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
$this->action->element('dt', null, _('Local views'));
$this->action->elementStart('dd');
// action => array('prompt', 'title')
$menu = array();
if (!common_config('site','openidonly')) {
$menu['login'] = array(_('Login'),
_('Login with a username and password'));
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
$menu['register'] = array(_('Register'),
_('Sign up for a new account'));
}
}
if (common_config('openid', 'enabled')) {
$menu['openidlogin'] = array(_('OpenID'),
_('Login or register with OpenID'));
}
$menu['FBConnectLogin'] = array(_('Facebook'),
_('Login or register using Facebook'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
$action_name === $menuaction);
}
$this->action->elementEnd('ul');
$this->action->elementEnd('dd');
$this->action->elementEnd('dl');
}
}

View File

@ -1,115 +0,0 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Menu for login group of actions
*
* 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 Menu
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/widget.php';
/**
* A widget for showing the connect group local nav menu
*
* @category Output
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see Widget
*/
class FBCSettingsNav extends Widget
{
var $action = null;
/**
* Construction
*
* @param Action $action current action, used for output
*/
function __construct($action=null)
{
parent::__construct($action);
$this->action = $action;
}
/**
* Show the menu
*
* @return void
*/
function show()
{
$this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
$this->action->element('dt', null, _('Local views'));
$this->action->elementStart('dd');
# action => array('prompt', 'title')
$menu = array();
if (common_config('xmpp', 'enabled')) {
$menu['imsettings'] =
array(_('IM'),
_('Updates by instant messenger (IM)'));
}
if (common_config('sms', 'enabled')) {
$menu['smssettings'] =
array(_('SMS'),
_('Updates by SMS'));
}
if (common_config('twitter', 'enabled')) {
$menu['twittersettings'] =
array(_('Twitter'),
_('Twitter integration options'));
}
$menu['FBConnectSettings'] =
array(_('Facebook'),
_('Facebook Connect settings'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
$action_name === $menuaction);
}
$this->action->elementEnd('ul');
$this->action->elementEnd('dd');
$this->action->elementEnd('dl');
}
}

View File

@ -21,7 +21,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php';
class FBConnectLoginAction extends Action
@ -65,4 +64,9 @@ class FBConnectLoginAction extends Action
$this->elementEnd('fieldset');
}
function showLocalNav()
{
$nav = new LoginGroupNav($this);
$nav->show();
}
}

View File

@ -394,37 +394,43 @@ class FacebookPlugin extends Plugin
return true;
}
/**
* Alter the local nav menu to have a Facebook Connect login and
* settings pages
/*
* Add a login tab for Facebook Connect
*
* @param Action $action the current action
* @param Action &action the current action
*
* @return void
*
*/
function onStartShowLocalNavBlock($action)
function onEndLoginGroupNav(&$action)
{
$action_name = get_class($action);
$login_actions = array('LoginAction', 'RegisterAction',
'OpenidloginAction', 'FBConnectLoginAction');
$action_name = $action->trimmed('action');
if (in_array($action_name, $login_actions)) {
$nav = new FBCLoginGroupNav($action);
$nav->show();
return false;
}
$action->menuItem(common_local_url('FBConnectLogin'),
_('Facebook'),
_('Login or register using Facebook'),
'FBConnectLogin' === $action_name);
$connect_actions = array('SmssettingsAction', 'ImsettingsAction',
'TwittersettingsAction', 'FBConnectSettingsAction');
return true;
}
if (in_array($action_name, $connect_actions)) {
$nav = new FBCSettingsNav($action);
$nav->show();
return false;
}
/*
* Add a tab for managing Facebook Connect settings
*
* @param Action &action the current action
*
* @return void
*/
function onEndConnectSettingsNav(&$action)
{
$action_name = $action->trimmed('action');
$action->menuItem(common_local_url('FBConnectSettings'),
_('Facebook'),
_('Facebook Connect Settings'),
$action_name === 'FBConnectSettings');
return true;
}

View File

@ -52,21 +52,21 @@ class FacebooksettingsAction extends FacebookAction
function saveSettings() {
$noticesync = $this->arg('noticesync');
$replysync = $this->arg('replysync');
$prefix = $this->trimmed('prefix');
$noticesync = $this->boolean('noticesync');
$replysync = $this->boolean('replysync');
$prefix = $this->trimmed('prefix');
$original = clone($this->flink);
$this->flink->set_flags($noticesync, $replysync, false, false);
$this->flink->set_flags($noticesync, false, $replysync, false);
$result = $this->flink->update($original);
if ($prefix == '' || $prefix == '0') {
// Facebook bug: saving empty strings to prefs now fails
// http://bugs.developers.facebook.com/show_bug.cgi?id=7110
$trimmed = $prefix . ' ';
// Facebook bug: saving empty strings to prefs now fails
// http://bugs.developers.facebook.com/show_bug.cgi?id=7110
$trimmed = $prefix . ' ';
} else {
$trimmed = substr($prefix, 0, 128);
}
$trimmed = substr($prefix, 0, 128);
}
$this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX,
$trimmed);

View File

@ -0,0 +1,117 @@
<?php
/*
StatusNet Plugin: 0.9
Plugin Name: FeedSub
Plugin URI: http://status.net/wiki/Feed_subscription
Description: FeedSub allows subscribing to real-time updates from external feeds supporting PubHubSubbub protocol.
Version: 0.1
Author: Brion Vibber <brion@status.net>
Author URI: http://status.net/
*/
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 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/>.
*/
/**
* @package FeedSubPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
define('FEEDSUB_SERVICE', 100); // fixme -- avoid hardcoding these?
// We bundle the XML_Parse_Feed library...
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib');
class FeedSubException extends Exception
{
}
class FeedSubPlugin extends Plugin
{
/**
* Hook for RouterInitialized event.
*
* @param Net_URL_Mapper $m path-to-action mapper
* @return boolean hook return
*/
function onRouterInitialized($m)
{
$m->connect('feedsub/callback/:feed',
array('action' => 'feedsubcallback'),
array('feed' => '[0-9]+'));
$m->connect('settings/feedsub',
array('action' => 'feedsubsettings'));
return true;
}
/**
* Add the feed settings page to the Connect Settings menu
*
* @param Action &$action The calling page
*
* @return boolean hook return
*/
function onEndConnectSettingsNav(&$action)
{
$action_name = $action->trimmed('action');
$action->menuItem(common_local_url('feedsubsettings'),
dgettext('FeebSubPlugin', 'Feeds'),
dgettext('FeedSubPlugin', 'Feed subscription options'),
$action_name === 'feedsubsettings');
return true;
}
/**
* Automatically load the actions and libraries used by the plugin
*
* @param Class $cls the class
*
* @return boolean hook return
*
*/
function onAutoload($cls)
{
$base = dirname(__FILE__);
$lower = strtolower($cls);
$files = array("$base/$lower.php");
if (substr($lower, -6) == 'action') {
$files[] = "$base/actions/" . substr($lower, 0, -6) . ".php";
}
foreach ($files as $file) {
if (file_exists($file)) {
include_once $file;
return false;
}
}
return true;
}
/*
// auto increment seems to be broken
function onCheckSchema() {
$schema = Schema::get();
$schema->ensureDataObject('Feedinfo');
return true;
}
*/
}

24
plugins/FeedSub/README Normal file
View File

@ -0,0 +1,24 @@
Plugin to support importing updates from external RSS and Atom feeds into your timeline.
Uses PubSubHubbub for push feed updates; currently non-PuSH feeds cannot be subscribed.
Todo:
* set feed icon avatar for actual profiles as well as for preview
* use channel image and/or favicon for avatar?
* garbage-collect subscriptions that are no longer being used
* administrative way to kill feeds?
* functional l10n
* clean up subscription form look and workflow
* use ajax for test/preview in subscription form
* rssCloud support? (Does anything use it that doesn't support PuSH as well?)
* possibly a polling daemon to support non-PuSH feeds?
* likely problems with multiple feeds from the same site, such as category feeds on a blog
(currently each feed would publish a separate notice on a separate profile, but pointing to the same post URI.)
(could use the local URI I guess, but that's so icky!)
* problems with Atom feeds that list <link rel="alternate" href="..."/> but don't have the type
(such as http://atomgen.appspot.com/feed/5 demo feed); currently it's not recognized and we end up with the feed's master URI
* make it easier to see what you're subscribed to and unsub from things
* saner treatment of fullname/nickname?
* make use of tags/categories from feeds
* update feed profile data when it changes
* XML_Feed_Parser has major problems with category and link tags; consider replacing?

View File

@ -0,0 +1,100 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 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/>.
*/
/**
* @package FeedSubPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class FeedSubCallbackAction extends Action
{
function handle()
{
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
} else {
$this->handleGet();
}
}
/**
* Handler for POST content updates from the hub
*/
function handlePost()
{
$feedid = $this->arg('feed');
common_log(LOG_INFO, "POST for feed id $feedid");
if (!$feedid) {
throw new ServerException('Empty or invalid feed id', 400);
}
$feedinfo = Feedinfo::staticGet('id', $feedid);
if (!$feedinfo) {
throw new ServerException('Unknown feed id ' . $feedid, 400);
}
$post = file_get_contents('php://input');
$feedinfo->postUpdates($post);
}
/**
* Handler for GET verification requests from the hub
*/
function handleGet()
{
$mode = $this->arg('hub_mode');
$topic = $this->arg('hub_topic');
$challenge = $this->arg('hub_challenge');
$lease_seconds = $this->arg('hub_lease_seconds');
$verify_token = $this->arg('hub_verify_token');
if ($mode != 'subscribe' && $mode != 'unsubscribe') {
common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with mode \"$mode\"");
throw new ServerException("Bogus hub callback: bad mode", 404);
}
$feedinfo = Feedinfo::staticGet('feeduri', $topic);
if (!$feedinfo) {
common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback for unknown feed $topic");
throw new ServerException("Bogus hub callback: unknown feed", 404);
}
# Can't currently set the token in our sub api
#if ($feedinfo->verify_token !== $verify_token) {
# common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback with bad token \"$verify_token\" for feed $topic");
# throw new ServerError("Bogus hub callback: bad token", 404);
#}
// OK!
common_log(LOG_INFO, __METHOD__ . ': sub confirmed');
$feedinfo->sub_start = common_sql_date(time());
if ($lease_seconds > 0) {
$feedinfo->sub_end = common_sql_date(time() + $lease_seconds);
} else {
$feedinfo->sub_end = null;
}
$feedinfo->update();
print $challenge;
}
}

View File

@ -0,0 +1,257 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 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/>.
*/
/**
* @package FeedSubPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class FeedSubSettingsAction extends ConnectSettingsAction
{
protected $feedurl;
protected $preview;
protected $munger;
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
return dgettext('FeedSubPlugin', 'Feed subscriptions');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return dgettext('FeedSubPlugin',
'You can subscribe to feeds from other sites; ' .
'updates will appear in your personal timeline.');
}
/**
* Content area of the page
*
* Shows a form for associating a Twitter account with this
* StatusNet account. Also lets the user set preferences.
*
* @return void
*/
function showContent()
{
$user = common_current_user();
$profile = $user->getProfile();
$fuser = null;
$flink = Foreign_link::getByUserID($user->id, FEEDSUB_SERVICE);
if (!empty($flink)) {
$fuser = $flink->getForeignUser();
}
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_feedsub',
'class' => 'form_settings',
'action' =>
common_local_url('feedsubsettings')));
$this->hidden('token', common_session_token());
$this->elementStart('fieldset', array('id' => 'settings_feeds'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'settings_twitter_login_button'));
$this->input('feedurl', _('Feed URL'), $this->feedurl, _('Enter the URL of a PubSubHubbub-enabled feed'));
$this->elementEnd('li');
$this->elementEnd('ul');
if ($this->preview) {
$this->submit('subscribe', dgettext('FeedSubPlugin', 'Subscribe'));
} else {
$this->submit('validate', dgettext('FeedSubPlugin', 'Continue'));
}
$this->elementEnd('fieldset');
$this->elementEnd('form');
if ($this->preview) {
$this->previewFeed();
}
}
/**
* Handle posts to this form
*
* Based on the button that was pressed, muxes out to other functions
* to do the actual task requested.
*
* All sub-functions reload the form with a message -- success or failure.
*
* @return void
*/
function handlePost()
{
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. '.
'Try again, please.'));
return;
}
if ($this->arg('validate')) {
$this->validateAndPreview();
} else if ($this->arg('subscribe')) {
$this->saveFeed();
} else {
$this->showForm(_('Unexpected form submission.'));
}
}
/**
* Set up and add a feed
*
* @return boolean true if feed successfully read
* Sends you back to input form if not.
*/
function validateFeed()
{
$feedurl = trim($this->arg('feedurl'));
if ($feedurl == '') {
$this->showForm(dgettext('FeedSubPlugin',
'Empty feed URL!'));
return;
}
$this->feedurl = $feedurl;
// Get the canonical feed URI and check it
try {
$discover = new FeedDiscovery();
$uri = $discover->discoverFromURL($feedurl);
} catch (FeedSubBadURLException $e) {
$this->showForm(dgettext('FeedSubPlugin', 'Invalid URL or could not reach server.'));
return false;
} catch (FeedSubBadResponseException $e) {
$this->showForm(dgettext('FeedSubPlugin', 'Cannot read feed; server returned error.'));
return false;
} catch (FeedSubEmptyException $e) {
$this->showForm(dgettext('FeedSubPlugin', 'Cannot read feed; server returned an empty page.'));
return false;
} catch (FeedSubBadHTMLException $e) {
$this->showForm(dgettext('FeedSubPlugin', 'Bad HTML, could not find feed link.'));
return false;
} catch (FeedSubNoFeedException $e) {
$this->showForm(dgettext('FeedSubPlugin', 'Could not find a feed linked from this URL.'));
return false;
} catch (FeedSubUnrecognizedTypeException $e) {
$this->showForm(dgettext('FeedSubPlugin', 'Not a recognized feed type.'));
return false;
} catch (FeedSubException $e) {
// Any new ones we forgot about
$this->showForm(dgettext('FeedSubPlugin', 'Bad feed URL.'));
return false;
}
$this->munger = $discover->feedMunger();
$this->feedinfo = $this->munger->feedInfo();
if ($this->feedinfo->huburi == '') {
$this->showForm(dgettext('FeedSubPlugin', 'Feed is not PuSH-enabled; cannot subscribe.'));
return false;
}
return true;
}
function saveFeed()
{
if ($this->validateFeed()) {
$this->preview = true;
$this->feedinfo = Feedinfo::ensureProfile($this->munger);
// If not already in use, subscribe to updates via the hub
if ($this->feedinfo->sub_start) {
common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}");
} else {
$ok = $this->feedinfo->subscribe();
common_log(LOG_INFO, __METHOD__ . ": sub was $ok");
if (!$ok) {
$this->showForm(dgettext('FeedSubPlugin', 'Feed subscription failed! Bad response from hub.'));
return;
}
}
// And subscribe the current user to the local profile
$user = common_current_user();
$profile = $this->feedinfo->getProfile();
if ($user->isSubscribed($profile)) {
$this->showForm(dgettext('FeedSubPlugin', 'Already subscribed!'));
} elseif ($user->subscribeTo($profile)) {
$this->showForm(dgettext('FeedSubPlugin', 'Feed subscribed!'));
} else {
$this->showForm(dgettext('FeedSubPlugin', 'Feed subscription failed!'));
}
}
}
function validateAndPreview()
{
if ($this->validateFeed()) {
$this->preview = true;
$this->showForm(dgettext('FeedSubPlugin', 'Previewing feed:'));
}
}
function previewFeed()
{
$feedinfo = $this->munger->feedinfo();
$notice = $this->munger->notice(0, true); // preview
if ($notice) {
$this->element('b', null, 'Preview of latest post from this feed:');
$item = new NoticeList($notice, $this);
$item->show();
} else {
$this->element('b', null, 'No posts in this feed yet.');
}
}
function showScripts()
{
parent::showScripts();
$this->autofocus('feedurl');
}
}

View File

@ -0,0 +1,9 @@
XML_Feed_Parser 1.0.3 is not currently actively maintained, and has
a nasty bug which breaks getting the feed target link from WordPress
feeds and possibly others that are RSS2-formatted but include an
<atom:link> self-link element as well.
Patch from this bug report is included:
http://pear.php.net/bugs/bug.php?id=16416
If upgrading, be sure that fix is included with the future upgrade!

View File

@ -0,0 +1,351 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Key gateway class for XML_Feed_Parser package
*
* PHP versions 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category XML
* @package XML_Feed_Parser
* @author James Stewart <james@jystewart.net>
* @copyright 2005 James Stewart <james@jystewart.net>
* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL
* @version CVS: $Id: Parser.php,v 1.24 2006/08/15 13:04:00 jystewart Exp $
* @link http://pear.php.net/package/XML_Feed_Parser/
*/
/**
* XML_Feed_Parser_Type is an abstract class required by all of our
* feed types. It makes sense to load it here to keep the other files
* clean.
*/
require_once 'XML/Feed/Parser/Type.php';
/**
* We will throw exceptions when errors occur.
*/
require_once 'XML/Feed/Parser/Exception.php';
/**
* This is the core of the XML_Feed_Parser package. It identifies feed types
* and abstracts access to them. It is an iterator, allowing for easy access
* to the entire feed.
*
* @author James Stewart <james@jystewart.net>
* @version Release: 1.0.3
* @package XML_Feed_Parser
*/
class XML_Feed_Parser implements Iterator
{
/**
* This is where we hold the feed object
* @var Object
*/
private $feed;
/**
* To allow for extensions, we make a public reference to the feed model
* @var DOMDocument
*/
public $model;
/**
* A map between entry ID and offset
* @var array
*/
protected $idMappings = array();
/**
* A storage space for Namespace URIs.
* @var array
*/
private $feedNamespaces = array(
'rss2' => array(
'http://backend.userland.com/rss',
'http://backend.userland.com/rss2',
'http://blogs.law.harvard.edu/tech/rss'));
/**
* Detects feed types and instantiate appropriate objects.
*
* Our constructor takes care of detecting feed types and instantiating
* appropriate classes. For now we're going to treat Atom 0.3 as Atom 1.0
* but raise a warning. I do not intend to introduce full support for
* Atom 0.3 as it has been deprecated, but others are welcome to.
*
* @param string $feed XML serialization of the feed
* @param bool $strict Whether or not to validate the feed
* @param bool $suppressWarnings Trigger errors for deprecated feed types?
* @param bool $tidy Whether or not to try and use the tidy library on input
*/
function __construct($feed, $strict = false, $suppressWarnings = false, $tidy = false)
{
$this->model = new DOMDocument;
if (! $this->model->loadXML($feed)) {
if (extension_loaded('tidy') && $tidy) {
$tidy = new tidy;
$tidy->parseString($feed,
array('input-xml' => true, 'output-xml' => true));
$tidy->cleanRepair();
if (! $this->model->loadXML((string) $tidy)) {
throw new XML_Feed_Parser_Exception('Invalid input: this is not ' .
'valid XML');
}
} else {
throw new XML_Feed_Parser_Exception('Invalid input: this is not valid XML');
}
}
/* detect feed type */
$doc_element = $this->model->documentElement;
$error = false;
switch (true) {
case ($doc_element->namespaceURI == 'http://www.w3.org/2005/Atom'):
require_once 'XML/Feed/Parser/Atom.php';
require_once 'XML/Feed/Parser/AtomElement.php';
$class = 'XML_Feed_Parser_Atom';
break;
case ($doc_element->namespaceURI == 'http://purl.org/atom/ns#'):
require_once 'XML/Feed/Parser/Atom.php';
require_once 'XML/Feed/Parser/AtomElement.php';
$class = 'XML_Feed_Parser_Atom';
$error = 'Atom 0.3 deprecated, using 1.0 parser which won\'t provide ' .
'all options';
break;
case ($doc_element->namespaceURI == 'http://purl.org/rss/1.0/' ||
($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
&& $doc_element->childNodes->item(1)->namespaceURI ==
'http://purl.org/rss/1.0/')):
require_once 'XML/Feed/Parser/RSS1.php';
require_once 'XML/Feed/Parser/RSS1Element.php';
$class = 'XML_Feed_Parser_RSS1';
break;
case ($doc_element->namespaceURI == 'http://purl.org/rss/1.1/' ||
($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
&& $doc_element->childNodes->item(1)->namespaceURI ==
'http://purl.org/rss/1.1/')):
require_once 'XML/Feed/Parser/RSS11.php';
require_once 'XML/Feed/Parser/RSS11Element.php';
$class = 'XML_Feed_Parser_RSS11';
break;
case (($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1
&& $doc_element->childNodes->item(1)->namespaceURI ==
'http://my.netscape.com/rdf/simple/0.9/') ||
$doc_element->namespaceURI == 'http://my.netscape.com/rdf/simple/0.9/'):
require_once 'XML/Feed/Parser/RSS09.php';
require_once 'XML/Feed/Parser/RSS09Element.php';
$class = 'XML_Feed_Parser_RSS09';
break;
case ($doc_element->tagName == 'rss' and
$doc_element->hasAttribute('version') &&
$doc_element->getAttribute('version') == 0.91):
$error = 'RSS 0.91 has been superceded by RSS2.0. Using RSS2.0 parser.';
require_once 'XML/Feed/Parser/RSS2.php';
require_once 'XML/Feed/Parser/RSS2Element.php';
$class = 'XML_Feed_Parser_RSS2';
break;
case ($doc_element->tagName == 'rss' and
$doc_element->hasAttribute('version') &&
$doc_element->getAttribute('version') == 0.92):
$error = 'RSS 0.92 has been superceded by RSS2.0. Using RSS2.0 parser.';
require_once 'XML/Feed/Parser/RSS2.php';
require_once 'XML/Feed/Parser/RSS2Element.php';
$class = 'XML_Feed_Parser_RSS2';
break;
case (in_array($doc_element->namespaceURI, $this->feedNamespaces['rss2'])
|| $doc_element->tagName == 'rss'):
if (! $doc_element->hasAttribute('version') ||
$doc_element->getAttribute('version') != 2) {
$error = 'RSS version not specified. Parsing as RSS2.0';
}
require_once 'XML/Feed/Parser/RSS2.php';
require_once 'XML/Feed/Parser/RSS2Element.php';
$class = 'XML_Feed_Parser_RSS2';
break;
default:
throw new XML_Feed_Parser_Exception('Feed type unknown');
break;
}
if (! $suppressWarnings && ! empty($error)) {
trigger_error($error, E_USER_WARNING);
}
/* Instantiate feed object */
$this->feed = new $class($this->model, $strict);
}
/**
* Proxy to allow feed element names to be used as method names
*
* For top-level feed elements we will provide access using methods or
* attributes. This function simply passes on a request to the appropriate
* feed type object.
*
* @param string $call - the method being called
* @param array $attributes
*/
function __call($call, $attributes)
{
$attributes = array_pad($attributes, 5, false);
list($a, $b, $c, $d, $e) = $attributes;
return $this->feed->$call($a, $b, $c, $d, $e);
}
/**
* Proxy to allow feed element names to be used as attribute names
*
* To allow variable-like access to feed-level data we use this
* method. It simply passes along to __call() which in turn passes
* along to the relevant object.
*
* @param string $val - the name of the variable required
*/
function __get($val)
{
return $this->feed->$val;
}
/**
* Provides iteration functionality.
*
* Of course we must be able to iterate... This function simply increases
* our internal counter.
*/
function next()
{
if (isset($this->current_item) &&
$this->current_item <= $this->feed->numberEntries - 1) {
++$this->current_item;
} else if (! isset($this->current_item)) {
$this->current_item = 0;
} else {
return false;
}
}
/**
* Return XML_Feed_Type object for current element
*
* @return XML_Feed_Parser_Type Object
*/
function current()
{
return $this->getEntryByOffset($this->current_item);
}
/**
* For iteration -- returns the key for the current stage in the array.
*
* @return int
*/
function key()
{
return $this->current_item;
}
/**
* For iteration -- tells whether we have reached the
* end.
*
* @return bool
*/
function valid()
{
return $this->current_item < $this->feed->numberEntries;
}
/**
* For iteration -- resets the internal counter to the beginning.
*/
function rewind()
{
$this->current_item = 0;
}
/**
* Provides access to entries by ID if one is specified in the source feed.
*
* As well as allowing the items to be iterated over we want to allow
* users to be able to access a specific entry. This is one of two ways of
* doing that, the other being by offset. This method can be quite slow
* if dealing with a large feed that hasn't yet been processed as it
* instantiates objects for every entry until it finds the one needed.
*
* @param string $id Valid ID for the given feed format
* @return XML_Feed_Parser_Type|false
*/
function getEntryById($id)
{
if (isset($this->idMappings[$id])) {
return $this->getEntryByOffset($this->idMappings[$id]);
}
/*
* Since we have not yet encountered that ID, let's go through all the
* remaining entries in order till we find it.
* This is a fairly slow implementation, but it should work.
*/
return $this->feed->getEntryById($id);
}
/**
* Retrieve entry by numeric offset, starting from zero.
*
* As well as allowing the items to be iterated over we want to allow
* users to be able to access a specific entry. This is one of two ways of
* doing that, the other being by ID.
*
* @param int $offset The position of the entry within the feed, starting from 0
* @return XML_Feed_Parser_Type|false
*/
function getEntryByOffset($offset)
{
if ($offset < $this->feed->numberEntries) {
if (isset($this->feed->entries[$offset])) {
return $this->feed->entries[$offset];
} else {
try {
$this->feed->getEntryByOffset($offset);
} catch (Exception $e) {
return false;
}
$id = $this->feed->entries[$offset]->getID();
$this->idMappings[$id] = $offset;
return $this->feed->entries[$offset];
}
} else {
return false;
}
}
/**
* Retrieve version details from feed type class.
*
* @return void
* @author James Stewart
*/
function version()
{
return $this->feed->version;
}
/**
* Returns a string representation of the feed.
*
* @return String
**/
function __toString()
{
return $this->feed->__toString();
}
}
?>

View File

@ -0,0 +1,365 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Atom feed class for XML_Feed_Parser
*
* PHP versions 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category XML
* @package XML_Feed_Parser
* @author James Stewart <james@jystewart.net>
* @copyright 2005 James Stewart <james@jystewart.net>
* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
* @version CVS: $Id: Atom.php,v 1.29 2008/03/30 22:00:36 jystewart Exp $
* @link http://pear.php.net/package/XML_Feed_Parser/
*/
/**
* This is the class that determines how we manage Atom 1.0 feeds
*
* How we deal with constructs:
* date - return as unix datetime for use with the 'date' function unless specified otherwise
* text - return as is. optional parameter will give access to attributes
* person - defaults to name, but parameter based access
*
* @author James Stewart <james@jystewart.net>
* @version Release: 1.0.3
* @package XML_Feed_Parser
*/
class XML_Feed_Parser_Atom extends XML_Feed_Parser_Type
{
/**
* The URI of the RelaxNG schema used to (optionally) validate the feed
* @var string
*/
private $relax = 'atom.rnc';
/**
* We're likely to use XPath, so let's keep it global
* @var DOMXPath
*/
public $xpath;
/**
* When performing XPath queries we will use this prefix
* @var string
*/
private $xpathPrefix = '//';
/**
* The feed type we are parsing
* @var string
*/
public $version = 'Atom 1.0';
/**
* The class used to represent individual items
* @var string
*/
protected $itemClass = 'XML_Feed_Parser_AtomElement';
/**
* The element containing entries
* @var string
*/
protected $itemElement = 'entry';
/**
* Here we map those elements we're not going to handle individually
* to the constructs they are. The optional second parameter in the array
* tells the parser whether to 'fall back' (not apt. at the feed level) or
* fail if the element is missing. If the parameter is not set, the function
* will simply return false and leave it to the client to decide what to do.
* @var array
*/
protected $map = array(
'author' => array('Person'),
'contributor' => array('Person'),
'icon' => array('Text'),
'logo' => array('Text'),
'id' => array('Text', 'fail'),
'rights' => array('Text'),
'subtitle' => array('Text'),
'title' => array('Text', 'fail'),
'updated' => array('Date', 'fail'),
'link' => array('Link'),
'generator' => array('Text'),
'category' => array('Category'));
/**
* Here we provide a few mappings for those very special circumstances in
* which it makes sense to map back to the RSS2 spec. Key is RSS2 version
* value is an array consisting of the equivalent in atom and any attributes
* needed to make the mapping.
* @var array
*/
protected $compatMap = array(
'guid' => array('id'),
'links' => array('link'),
'tags' => array('category'),
'contributors' => array('contributor'));
/**
* Our constructor does nothing more than its parent.
*
* @param DOMDocument $xml A DOM object representing the feed
* @param bool (optional) $string Whether or not to validate this feed
*/
function __construct(DOMDocument $model, $strict = false)
{
$this->model = $model;
if ($strict) {
if (! $this->model->relaxNGValidateSource($this->relax)) {
throw new XML_Feed_Parser_Exception('Failed required validation');
}
}
$this->xpath = new DOMXPath($this->model);
$this->xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
$this->numberEntries = $this->count('entry');
}
/**
* Implement retrieval of an entry based on its ID for atom feeds.
*
* This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate
* is available, we also use that to store a reference to the entry in the array
* used by getEntryByOffset so that method does not have to seek out the entry
* if it's requested that way.
*
* @param string $id any valid Atom ID.
* @return XML_Feed_Parser_AtomElement
*/
function getEntryById($id)
{
if (isset($this->idMappings[$id])) {
return $this->entries[$this->idMappings[$id]];
}
$entries = $this->xpath->query("//atom:entry[atom:id='$id']");
if ($entries->length > 0) {
$xmlBase = $entries->item(0)->baseURI;
$entry = new $this->itemClass($entries->item(0), $this, $xmlBase);
if (in_array('evaluate', get_class_methods($this->xpath))) {
$offset = $this->xpath->evaluate("count(preceding-sibling::atom:entry)", $entries->item(0));
$this->entries[$offset] = $entry;
}
$this->idMappings[$id] = $entry;
return $entry;
}
}
/**
* Retrieves data from a person construct.
*
* Get a person construct. We default to the 'name' element but allow
* access to any of the elements.
*
* @param string $method The name of the person construct we want
* @param array $arguments An array which we hope gives a 'param'
* @return string|false
*/
protected function getPerson($method, $arguments)
{
$offset = empty($arguments[0]) ? 0 : $arguments[0];
$parameter = empty($arguments[1]['param']) ? 'name' : $arguments[1]['param'];
$section = $this->model->getElementsByTagName($method);
if ($parameter == 'url') {
$parameter = 'uri';
}
if ($section->length <= $offset) {
return false;
}
$param = $section->item($offset)->getElementsByTagName($parameter);
if ($param->length == 0) {
return false;
}
return $param->item(0)->nodeValue;
}
/**
* Retrieves an element's content where that content is a text construct.
*
* Get a text construct. When calling this method, the two arguments
* allowed are 'offset' and 'attribute', so $parser->subtitle() would
* return the content of the element, while $parser->subtitle(false, 'type')
* would return the value of the type attribute.
*
* @todo Clarify overlap with getContent()
* @param string $method The name of the text construct we want
* @param array $arguments An array which we hope gives a 'param'
* @return string
*/
protected function getText($method, $arguments)
{
$offset = empty($arguments[0]) ? 0: $arguments[0];
$attribute = empty($arguments[1]) ? false : $arguments[1];
$tags = $this->model->getElementsByTagName($method);
if ($tags->length <= $offset) {
return false;
}
$content = $tags->item($offset);
if (! $content->hasAttribute('type')) {
$content->setAttribute('type', 'text');
}
$type = $content->getAttribute('type');
if (! empty($attribute) and
! ($method == 'generator' and $attribute == 'name')) {
if ($content->hasAttribute($attribute)) {
return $content->getAttribute($attribute);
} else if ($attribute == 'href' and $content->hasAttribute('uri')) {
return $content->getAttribute('uri');
}
return false;
}
return $this->parseTextConstruct($content);
}
/**
* Extract content appropriately from atom text constructs
*
* Because of different rules applied to the content element and other text
* constructs, they are deployed as separate functions, but they share quite
* a bit of processing. This method performs the core common process, which is
* to apply the rules for different mime types in order to extract the content.
*
* @param DOMNode $content the text construct node to be parsed
* @return String
* @author James Stewart
**/
protected function parseTextConstruct(DOMNode $content)
{
if ($content->hasAttribute('type')) {
$type = $content->getAttribute('type');
} else {
$type = 'text';
}
if (strpos($type, 'text/') === 0) {
$type = 'text';
}
switch ($type) {
case 'text':
case 'html':
return $content->textContent;
break;
case 'xhtml':
$container = $content->getElementsByTagName('div');
if ($container->length == 0) {
return false;
}
$contents = $container->item(0);
if ($contents->hasChildNodes()) {
/* Iterate through, applying xml:base and store the result */
$result = '';
foreach ($contents->childNodes as $node) {
$result .= $this->traverseNode($node);
}
return $result;
}
break;
case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i', $type) > 0:
return $content;
break;
case 'application/octet-stream':
default:
return base64_decode(trim($content->nodeValue));
break;
}
return false;
}
/**
* Get a category from the entry.
*
* A feed or entry can have any number of categories. A category can have the
* attributes term, scheme and label.
*
* @param string $method The name of the text construct we want
* @param array $arguments An array which we hope gives a 'param'
* @return string
*/
function getCategory($method, $arguments)
{
$offset = empty($arguments[0]) ? 0: $arguments[0];
$attribute = empty($arguments[1]) ? 'term' : $arguments[1];
$categories = $this->model->getElementsByTagName('category');
if ($categories->length <= $offset) {
$category = $categories->item($offset);
if ($category->hasAttribute($attribute)) {
return $category->getAttribute($attribute);
}
}
return false;
}
/**
* This element must be present at least once with rel="feed". This element may be
* present any number of further times so long as there is no clash. If no 'rel' is
* present and we're asked for one, we follow the example of the Universal Feed
* Parser and presume 'alternate'.
*
* @param int $offset the position of the link within the container
* @param string $attribute the attribute name required
* @param array an array of attributes to search by
* @return string the value of the attribute
*/
function getLink($offset = 0, $attribute = 'href', $params = false)
{
if (is_array($params) and !empty($params)) {
$terms = array();
$alt_predicate = '';
$other_predicate = '';
foreach ($params as $key => $value) {
if ($key == 'rel' && $value == 'alternate') {
$alt_predicate = '[not(@rel) or @rel="alternate"]';
} else {
$terms[] = "@$key='$value'";
}
}
if (!empty($terms)) {
$other_predicate = '[' . join(' and ', $terms) . ']';
}
$query = $this->xpathPrefix . 'atom:link' . $alt_predicate . $other_predicate;
$links = $this->xpath->query($query);
} else {
$links = $this->model->getElementsByTagName('link');
}
if ($links->length > $offset) {
if ($links->item($offset)->hasAttribute($attribute)) {
$value = $links->item($offset)->getAttribute($attribute);
if ($attribute == 'href') {
$value = $this->addBase($value, $links->item($offset));
}
return $value;
} else if ($attribute == 'rel') {
return 'alternate';
}
}
return false;
}
}
?>

View File

@ -0,0 +1,261 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* AtomElement class for XML_Feed_Parser package
*
* PHP versions 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category XML
* @package XML_Feed_Parser
* @author James Stewart <james@jystewart.net>
* @copyright 2005 James Stewart <james@jystewart.net>
* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
* @version CVS: $Id: AtomElement.php,v 1.19 2007/03/26 12:43:11 jystewart Exp $
* @link http://pear.php.net/package/XML_Feed_Parser/
*/
/**
* This class provides support for atom entries. It will usually be called by
* XML_Feed_Parser_Atom with which it shares many methods.
*
* @author James Stewart <james@jystewart.net>
* @version Release: 1.0.3
* @package XML_Feed_Parser
*/
class XML_Feed_Parser_AtomElement extends XML_Feed_Parser_Atom
{
/**
* This will be a reference to the parent object for when we want
* to use a 'fallback' rule
* @var XML_Feed_Parser_Atom
*/
protected $parent;
/**
* When performing XPath queries we will use this prefix
* @var string
*/
private $xpathPrefix = '';
/**
* xml:base values inherited by the element
* @var string
*/
protected $xmlBase;
/**
* Here we provide a few mappings for those very special circumstances in
* which it makes sense to map back to the RSS2 spec or to manage other
* compatibilities (eg. with the Univeral Feed Parser). Key is the other version's
* name for the command, value is an array consisting of the equivalent in our atom
* api and any attributes needed to make the mapping.
* @var array
*/
protected $compatMap = array(
'guid' => array('id'),
'links' => array('link'),
'tags' => array('category'),
'contributors' => array('contributor'));
/**
* Our specific element map
* @var array
*/
protected $map = array(
'author' => array('Person', 'fallback'),
'contributor' => array('Person'),
'id' => array('Text', 'fail'),
'published' => array('Date'),
'updated' => array('Date', 'fail'),
'title' => array('Text', 'fail'),
'rights' => array('Text', 'fallback'),
'summary' => array('Text'),
'content' => array('Content'),
'link' => array('Link'),
'enclosure' => array('Enclosure'),
'category' => array('Category'));
/**
* Store useful information for later.
*
* @param DOMElement $element - this item as a DOM element
* @param XML_Feed_Parser_Atom $parent - the feed of which this is a member
*/
function __construct(DOMElement $element, $parent, $xmlBase = '')
{
$this->model = $element;
$this->parent = $parent;
$this->xmlBase = $xmlBase;
$this->xpathPrefix = "//atom:entry[atom:id='" . $this->id . "']/";
$this->xpath = $this->parent->xpath;
}
/**
* Provides access to specific aspects of the author data for an atom entry
*
* Author data at the entry level is more complex than at the feed level.
* If atom:author is not present for the entry we need to look for it in
* an atom:source child of the atom:entry. If it's not there either, then
* we look to the parent for data.
*
* @param array
* @return string
*/
function getAuthor($arguments)
{
/* Find out which part of the author data we're looking for */
if (isset($arguments['param'])) {
$parameter = $arguments['param'];
} else {
$parameter = 'name';
}
$test = $this->model->getElementsByTagName('author');
if ($test->length > 0) {
$item = $test->item(0);
return $item->getElementsByTagName($parameter)->item(0)->nodeValue;
}
$source = $this->model->getElementsByTagName('source');
if ($source->length > 0) {
$test = $this->model->getElementsByTagName('author');
if ($test->length > 0) {
$item = $test->item(0);
return $item->getElementsByTagName($parameter)->item(0)->nodeValue;
}
}
return $this->parent->getAuthor($arguments);
}
/**
* Returns the content of the content element or info on a specific attribute
*
* This element may or may not be present. It cannot be present more than
* once. It may have a 'src' attribute, in which case there's no content
* If not present, then the entry must have link with rel="alternate".
* If there is content we return it, if not and there's a 'src' attribute
* we return the value of that instead. The method can take an 'attribute'
* argument, in which case we return the value of that attribute if present.
* eg. $item->content("type") will return the type of the content. It is
* recommended that all users check the type before getting the content to
* ensure that their script is capable of handling the type of returned data.
* (data carried in the content element can be either 'text', 'html', 'xhtml',
* or any standard MIME type).
*
* @return string|false
*/
protected function getContent($method, $arguments = array())
{
$attribute = empty($arguments[0]) ? false : $arguments[0];
$tags = $this->model->getElementsByTagName('content');
if ($tags->length == 0) {
return false;
}
$content = $tags->item(0);
if (! $content->hasAttribute('type')) {
$content->setAttribute('type', 'text');
}
if (! empty($attribute)) {
return $content->getAttribute($attribute);
}
$type = $content->getAttribute('type');
if (! empty($attribute)) {
if ($content->hasAttribute($attribute))
{
return $content->getAttribute($attribute);
}
return false;
}
if ($content->hasAttribute('src')) {
return $content->getAttribute('src');
}
return $this->parseTextConstruct($content);
}
/**
* For compatibility, this method provides a mapping to access enclosures.
*
* The Atom spec doesn't provide for an enclosure element, but it is
* generally supported using the link element with rel='enclosure'.
*
* @param string $method - for compatibility with our __call usage
* @param array $arguments - for compatibility with our __call usage
* @return array|false
*/
function getEnclosure($method, $arguments = array())
{
$offset = isset($arguments[0]) ? $arguments[0] : 0;
$query = "//atom:entry[atom:id='" . $this->getText('id', false) .
"']/atom:link[@rel='enclosure']";
$encs = $this->parent->xpath->query($query);
if ($encs->length > $offset) {
try {
if (! $encs->item($offset)->hasAttribute('href')) {
return false;
}
$attrs = $encs->item($offset)->attributes;
$length = $encs->item($offset)->hasAttribute('length') ?
$encs->item($offset)->getAttribute('length') : false;
return array(
'url' => $attrs->getNamedItem('href')->value,
'type' => $attrs->getNamedItem('type')->value,
'length' => $length);
} catch (Exception $e) {
return false;
}
}
return false;
}
/**
* Get details of this entry's source, if available/relevant
*
* Where an atom:entry is taken from another feed then the aggregator
* is supposed to include an atom:source element which replicates at least
* the atom:id, atom:title, and atom:updated metadata from the original
* feed. Atom:source therefore has a very similar structure to atom:feed
* and if we find it we will return it as an XML_Feed_Parser_Atom object.
*
* @return XML_Feed_Parser_Atom|false
*/
function getSource()
{
$test = $this->model->getElementsByTagName('source');
if ($test->length == 0) {
return false;
}
$source = new XML_Feed_Parser_Atom($test->item(0));
}
/**
* Get the entry as an XML string
*
* Return an XML serialization of the feed, should it be required. Most
* users however, will already have a serialization that they used when
* instantiating the object.
*
* @return string XML serialization of element
*/
function __toString()
{
$simple = simplexml_import_dom($this->model);
return $simple->asXML();
}
}
?>

View File

@ -0,0 +1,42 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Keeps the exception class for XML_Feed_Parser.
*
* PHP versions 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category XML
* @package XML_Feed_Parser
* @author James Stewart <james@jystewart.net>
* @copyright 2005 James Stewart <james@jystewart.net>
* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL
* @version CVS: $Id: Exception.php,v 1.3 2005/11/07 01:52:35 jystewart Exp $
* @link http://pear.php.net/package/XML_Feed_Parser/
*/
/**
* We are extending PEAR_Exception
*/
require_once 'PEAR/Exception.php';
/**
* XML_Feed_Parser_Exception is a simple extension of PEAR_Exception, existing
* to help with identification of the source of exceptions.
*
* @author James Stewart <james@jystewart.net>
* @version Release: 1.0.3
* @package XML_Feed_Parser
*/
class XML_Feed_Parser_Exception extends PEAR_Exception
{
}
?>

View File

@ -0,0 +1,214 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* RSS0.9 class for XML_Feed_Parser
*
* PHP versions 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category XML
* @package XML_Feed_Parser
* @author James Stewart <james@jystewart.net>
* @copyright 2005 James Stewart <james@jystewart.net>
* @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1
* @version CVS: $Id: RSS09.php,v 1.5 2006/07/26 21:18:46 jystewart Exp $
* @link http://pear.php.net/package/XML_Feed_Parser/
*/
/**
* This class handles RSS0.9 feeds.
*
* @author James Stewart <james@jystewart.net>
* @version Release: 1.0.3
* @package XML_Feed_Parser
* @todo Find a Relax NG URI we can use
*/
class XML_Feed_Parser_RSS09 extends XML_Feed_Parser_Type
{
/**
* The URI of the RelaxNG schema used to (optionally) validate the feed
* @var string
*/
private $relax = '';
/**
* We're likely to use XPath, so let's keep it global
* @var DOMXPath
*/
protected $xpath;
/**
* The feed type we are parsing
* @var string
*/
public $version = 'RSS 0.9';
/**
* The class used to represent individual items
* @var string
*/
protected $itemClass = 'XML_Feed_Parser_RSS09Element';
/**
* The element containing entries
* @var string
*/
protected $itemElement = 'item';
/**
* Here we map those elements we're not going to handle individually
* to the constructs they are. The optional second parameter in the array
* tells the parser whether to 'fall back' (not apt. at the feed level) or
* fail if the element is missing. If the parameter is not set, the function
* will simply return false and leave it to the client to decide what to do.
* @var array
*/
protected $map = array(
'title' => array('Text'),
'link' => array('Text'),
'description' => array('Text'),
'image' => array('Image'),
'textinput' => array('TextInput'));
/**
* Here we map some elements to their atom equivalents. This is going to be
* quite tricky to pull off effectively (and some users' methods may vary)
* but is worth trying. The key is the atom version, the value is RSS2.
* @var array
*/
protected $compatMap = array(
'title' => array('title'),
'link' => array('link'),
'subtitle' => array('description'));
/**
* We will be working with multiple namespaces and it is useful to
* keep them together
* @var array
*/
protected $namespaces = array(
'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
/**
* Our constructor does nothing more than its parent.
*
* @todo RelaxNG validation
* @param DOMDocument $xml A DOM object representing the feed
* @param bool (optional) $string Whether or not to validate this feed
*/
function __construct(DOMDocument $model, $strict = false)
{
$this->model = $model;
$this->xpath = new DOMXPath($model);
foreach ($this->namespaces as $key => $value) {
$this->xpath->registerNamespace($key, $value);
}
$this->numberEntries = $this->count('item');
}
/**
* Included for compatibility -- will not work with RSS 0.9
*
* This is not something that will work with RSS0.9 as it does not have
* clear restrictions on the global uniqueness of IDs.
*
* @param string $id any valid ID.
* @return false
*/
function getEntryById($id)
{
return false;
}
/**
* Get details of the image associated with the feed.
*
* @return array|false an array simply containing the child elements
*/
protected function getImage()
{
$images = $this->model->getElementsByTagName('image');
if ($images->length > 0) {
$image = $images->item(0);
$details = array();
if ($image->hasChildNodes()) {
$details = array(
'title' => $image->getElementsByTagName('title')->item(0)->value,
'link' => $image->getElementsByTagName('link')->item(0)->value,
'url' => $image->getElementsByTagName('url')->item(0)->value);
} else {
$details = array('title' => false,
'link' => false,
'url' => $image->attributes->getNamedItem('resource')->nodeValue);
}
$details = array_merge($details,
array('description' => false, 'height' => false, 'width' => false));
if (! empty($details)) {
return $details;
}
}
return false;
}
/**
* The textinput element is little used, but in the interests of
* completeness we will support it.
*
* @return array|false
*/
protected function getTextInput()
{
$inputs = $this->model->getElementsByTagName('textinput');
if ($inputs->length > 0) {
$input = $inputs->item(0);
$results = array();
$results['title'] = isset(
$input->getElementsByTagName('title')->item(0)->value) ?
$input->getElementsByTagName('title')->item(0)->value : null;
$results['description'] = isset(
$input->getElementsByTagName('description')->item(0)->value) ?
$input->getElementsByTagName('description')->item(0)->value : null;
$results['name'] = isset(
$input->getElementsByTagName('name')->item(0)->value) ?
$input->getElementsByTagName('name')->item(0)->value : null;
$results['link'] = isset(
$input->getElementsByTagName('link')->item(0)->value) ?
$input->getElementsByTagName('link')->item(0)->value : null;
if (empty($results['link']) &&
$input->attributes->getNamedItem('resource')) {
$results['link'] = $input->attributes->getNamedItem('resource')->nodeValue;
}
if (! empty($results)) {
return $results;
}
}
return false;
}
/**
* Get details of a link from the feed.
*
* In RSS1 a link is a text element but in order to ensure that we resolve
* URLs properly we have a special function for them.
*
* @return string
*/
function getLink($offset = 0, $attribute = 'href', $params = false)
{
$links = $this->model->getElementsByTagName('link');
if ($links->length <= $offset) {
return false;
}
$link = $links->item($offset);
return $this->addBase($link->nodeValue, $link);
}
}
?>

Some files were not shown because too many files have changed in this diff Show More