diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/action.php | 3 | ||||
-rw-r--r-- | lib/api.php | 22 | ||||
-rw-r--r-- | lib/atom10entry.php | 106 | ||||
-rw-r--r-- | lib/atom10feed.php | 298 | ||||
-rw-r--r-- | lib/atomgroupnoticefeed.php | 67 | ||||
-rw-r--r-- | lib/atomnoticefeed.php | 105 | ||||
-rw-r--r-- | lib/atomusernoticefeed.php | 66 | ||||
-rw-r--r-- | lib/cache.php | 4 | ||||
-rw-r--r-- | lib/default.php | 13 | ||||
-rw-r--r-- | lib/error.php | 10 | ||||
-rw-r--r-- | lib/grouplist.php | 4 | ||||
-rw-r--r-- | lib/groupsection.php | 3 | ||||
-rw-r--r-- | lib/htmloutputter.php | 15 | ||||
-rw-r--r-- | lib/httpclient.php | 5 | ||||
-rw-r--r-- | lib/mysqlschema.php | 538 | ||||
-rw-r--r-- | lib/noticelist.php | 11 | ||||
-rw-r--r-- | lib/noticesection.php | 1 | ||||
-rw-r--r-- | lib/oauthclient.php | 88 | ||||
-rw-r--r-- | lib/pgsqlschema.php | 503 | ||||
-rw-r--r-- | lib/profilelist.php | 4 | ||||
-rw-r--r-- | lib/profilesection.php | 2 | ||||
-rw-r--r-- | lib/right.php | 1 | ||||
-rw-r--r-- | lib/router.php | 9 | ||||
-rw-r--r-- | lib/schema.php | 58 | ||||
-rw-r--r-- | lib/statusnet.php | 13 | ||||
-rw-r--r-- | lib/theme.php | 15 | ||||
-rw-r--r-- | lib/userprofile.php | 9 | ||||
-rw-r--r-- | lib/util.php | 72 |
28 files changed, 1943 insertions, 102 deletions
diff --git a/lib/action.php b/lib/action.php index cc4f4aad0..b85f353a3 100644 --- a/lib/action.php +++ b/lib/action.php @@ -405,6 +405,7 @@ class Action extends HTMLOutputter // lawsuit 'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'), 'alt' => common_config('site', 'name'))); } + $this->text(' '); $this->element('span', array('class' => 'fn org'), common_config('site', 'name')); $this->elementEnd('a'); Event::handle('EndAddressData', array($this)); @@ -822,12 +823,14 @@ class Action extends HTMLOutputter // lawsuit 'alt' => common_config('license', 'title'), 'width' => '80', 'height' => '15')); + $this->text(' '); //TODO: This is dirty: i18n $this->text(_('All '.common_config('site', 'name').' content and data are available under the ')); $this->element('a', array('class' => 'license', 'rel' => 'external license', 'href' => common_config('license', 'url')), common_config('license', 'title')); + $this->text(' '); $this->text(_('license.')); $this->elementEnd('p'); break; diff --git a/lib/api.php b/lib/api.php index f81975216..22eef7436 100644 --- a/lib/api.php +++ b/lib/api.php @@ -77,6 +77,7 @@ class ApiAction extends Action function prepare($args) { + StatusNet::setApi(true); // reduce exception reports to aid in debugging parent::prepare($args); $this->format = $this->arg('format'); @@ -1102,7 +1103,7 @@ class ApiAction extends Action } } - function serverError($msg, $code = 500, $content_type = 'json') + function serverError($msg, $code = 500, $content_type = 'xml') { $action = $this->trimmed('action'); @@ -1153,7 +1154,6 @@ class ApiAction extends Action $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US', 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0')); - Event::handle('StartApiAtom', array($this)); } function endTwitterAtom() @@ -1320,4 +1320,22 @@ class ApiAction extends Action } } + function getSelfUri($action, $aargs) + { + parse_str($_SERVER['QUERY_STRING'], $params); + $pstring = ''; + if (!empty($params)) { + unset($params['p']); + $pstring = http_build_query($params); + } + + $uri = common_local_url($action, $aargs); + + if (!empty($pstring)) { + $uri .= '?' . $pstring; + } + + return $uri; + } + } diff --git a/lib/atom10entry.php b/lib/atom10entry.php new file mode 100644 index 000000000..5710c80fc --- /dev/null +++ b/lib/atom10entry.php @@ -0,0 +1,106 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for building / manipulating an Atom entry in memory + * + * 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 Feed + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2010 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); +} + +class Atom10EntryException extends Exception +{ +} + +/** + * Class for manipulating an Atom entry in memory. Get the entry as an XML + * string with Atom10Entry::getString(). + * + * @category Feed + * @package StatusNet + * @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/ + */ +class Atom10Entry extends XMLStringer +{ + private $namespaces; + private $categories; + private $content; + private $contributors; + private $id; + private $links; + private $published; + private $rights; + private $source; + private $summary; + private $title; + + function __construct($indent = true) { + parent::__construct($indent); + $this->namespaces = array(); + } + + function addNamespace($namespace, $uri) + { + $ns = array($namespace => $uri); + $this->namespaces = array_merge($this->namespaces, $ns); + } + + function initEntry() + { + + } + + function endEntry() + { + + } + + /** + * Check that all required elements have been set, etc. + * Throws an Atom10EntryException if something's missing. + * + * @return void + */ + function validate + { + + } + + function getString() + { + $this->validate(); + + $this->initEntry(); + $this->renderEntries(); + $this->endEntry(); + + return $this->xw->outputMemory(); + } + +}
\ No newline at end of file diff --git a/lib/atom10feed.php b/lib/atom10feed.php new file mode 100644 index 000000000..14a3beb83 --- /dev/null +++ b/lib/atom10feed.php @@ -0,0 +1,298 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for building an Atom feed in memory + * + * 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 Feed + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2010 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); +} + +class Atom10FeedException extends Exception +{ +} + +/** + * Class for building an Atom feed in memory. Get the finished doc + * as a string with Atom10Feed::getString(). + * + * @category Feed + * @package StatusNet + * @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/ + */ +class Atom10Feed extends XMLStringer +{ + public $xw; + private $namespaces; + private $authors; + private $subject; + private $categories; + private $contributors; + private $generator; + private $icon; + private $links; + private $logo; + private $rights; + private $subtitle; + private $title; + private $published; + private $updated; + private $entries; + + /** + * Constructor + * + * @param boolean $indent flag to turn indenting on or off + * + * @return void + */ + function __construct($indent = true) { + parent::__construct($indent); + $this->namespaces = array(); + $this->authors = array(); + $this->links = array(); + $this->entries = array(); + $this->addNamespace('xmlns', 'http://www.w3.org/2005/Atom'); + } + + /** + * Add another namespace to the feed + * + * @param string $namespace the namespace + * @param string $uri namspace uri + * + * @return void + */ + function addNamespace($namespace, $uri) + { + $ns = array($namespace => $uri); + $this->namespaces = array_merge($this->namespaces, $ns); + } + + function addAuthor($name, $uri = null, $email = null) + { + $xs = new XMLStringer(true); + + $xs->elementStart('author'); + + if (!empty($name)) { + $xs->element('name', null, $name); + } else { + throw new Atom10FeedException( + 'author element must contain a name element.' + ); + } + + if (!is_null($uri)) { + $xs->element('uri', null, $uri); + } + + if (!is_null(email)) { + $xs->element('email', null, $email); + } + + $xs->elementEnd('author'); + + array_push($this->authors, $xs->getString()); + } + + /** + * Add an Author to the feed via raw XML string + * + * @param string $xmlAuthor An XML string representation author + * + * @return void + */ + function addAuthorRaw($xmlAuthor) + { + array_push($this->authors, $xmlAuthor); + } + + function renderAuthors() + { + foreach ($this->authors as $author) { + $this->raw($author); + } + } + + /** + * Add a activity feed subject via raw XML string + * + * @param string $xmlSubject An XML string representation of the subject + * + * @return void + */ + function setActivitySubject($xmlSubject) + { + $this->subject = $xmlSubject; + } + + function getNamespaces() + { + return $this->namespaces; + } + + function initFeed() + { + $this->xw->startDocument('1.0', 'UTF-8'); + $commonAttrs = array('xml:lang' => 'en-US'); + $commonAttrs = array_merge($commonAttrs, $this->namespaces); + $this->elementStart('feed', $commonAttrs); + + $this->element('id', null, $this->id); + $this->element('title', null, $this->title); + $this->element('subtitle', null, $this->subtitle); + + if (!empty($this->logo)) { + $this->element('logo', null, $this->logo); + } + + $this->element('updated', null, $this->updated); + + $this->renderAuthors(); + + $this->renderLinks(); + } + + /** + * Check that all required elements have been set, etc. + * Throws an Atom10FeedException if something's missing. + * + * @return void + */ + function validate() + { + } + + function renderLinks() + { + foreach ($this->links as $attrs) + { + $this->element('link', $attrs, null); + } + } + + function addEntryRaw($xmlEntry) + { + array_push($this->entries, $xmlEntry); + } + + function addEntry($entry) + { + array_push($this->entries, $entry->getString()); + } + + function renderEntries() + { + foreach ($this->entries as $entry) { + $this->raw($entry); + } + } + + function endFeed() + { + $this->elementEnd('feed'); + $this->xw->endDocument(); + } + + function getString() + { + if (Event::handle('StartApiAtom', array($this))) { + + $this->validate(); + $this->initFeed(); + + if (!empty($this->subject)) { + $this->raw($this->subject); + } + + $this->renderEntries(); + $this->endFeed(); + + Event::handle('EndApiAtom', array($this)); + } + + return $this->xw->outputMemory(); + } + + function setId($id) + { + $this->id = $id; + } + + function setTitle($title) + { + $this->title = $title; + } + + function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } + + function setLogo($logo) + { + $this->logo = $logo; + } + + function setUpdated($dt) + { + $this->updated = common_date_iso8601($dt); + } + + function setPublished($dt) + { + $this->published = common_date_iso8601($dt); + } + + /** + * Adds a link element into the Atom document + * + * Assumes you want rel="alternate" and type="text/html" unless + * you send in $otherAttrs. + * + * @param string $uri the uri the href needs to point to + * @param array $otherAttrs other attributes to stick in + * + * @return void + */ + function addLink($uri, $otherAttrs = null) { + $attrs = array('href' => $uri); + + if (is_null($otherAttrs)) { + $attrs['rel'] = 'alternate'; + $attrs['type'] = 'text/html'; + } else { + $attrs = array_merge($attrs, $otherAttrs); + } + + array_push($this->links, $attrs); + } + +} diff --git a/lib/atomgroupnoticefeed.php b/lib/atomgroupnoticefeed.php new file mode 100644 index 000000000..52ee4c7d6 --- /dev/null +++ b/lib/atomgroupnoticefeed.php @@ -0,0 +1,67 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for building an in-memory Atom feed for a particular group's + * timeline. + * + * 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 Feed + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2010 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); +} + +/** + * Class for group notice feeds. May contains a reference to the group. + * + * @category Feed + * @package StatusNet + * @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/ + */ +class AtomGroupNoticeFeed extends AtomNoticeFeed +{ + private $group; + + /** + * Constructor + * + * @param Group $group the group for the feed (optional) + * @param boolean $indent flag to turn indenting on or off + * + * @return void + */ + function __construct($group = null, $indent = true) { + parent::__construct($indent); + $this->group = $group; + } + + function getGroup() + { + return $this->group; + } + +} diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php new file mode 100644 index 000000000..b7a60bde6 --- /dev/null +++ b/lib/atomnoticefeed.php @@ -0,0 +1,105 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for building an Atom feed from a collection of notices + * + * 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 Feed + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2010 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); +} + +/** + * Class for creating a feed that represents a collection of notices. Builds the + * feed in memory. Get the feed as a string with AtomNoticeFeed::getString(). + * + * @category Feed + * @package StatusNet + * @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/ + */ +class AtomNoticeFeed extends Atom10Feed +{ + function __construct($indent = true) { + parent::__construct($indent); + + // Feeds containing notice info use these namespaces + + $this->addNamespace( + 'xmlns:thr', + 'http://purl.org/syndication/thread/1.0' + ); + + $this->addNamespace( + 'xmlns:georss', + 'http://www.georss.org/georss' + ); + + $this->addNamespace( + 'xmlns:activity', + 'http://activitystrea.ms/spec/1.0/' + ); + + // XXX: What should the uri be? + $this->addNamespace( + 'xmlns:ostatus', + 'http://ostatus.org/schema/1.0' + ); + } + + /** + * Add more than one Notice to the feed + * + * @param mixed $notices an array of Notice objects or handle + * + */ + function addEntryFromNotices($notices) + { + if (is_array($notices)) { + foreach ($notices as $notice) { + $this->addEntryFromNotice($notice); + } + } else { + while ($notices->fetch()) { + $this->addEntryFromNotice($notices); + } + } + } + + /** + * Add a single Notice to the feed + * + * @param Notice $notice a Notice to add + */ + function addEntryFromNotice($notice) + { + $this->addEntryRaw($notice->asAtomEntry()); + } + +} + + diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php new file mode 100644 index 000000000..9f224325c --- /dev/null +++ b/lib/atomusernoticefeed.php @@ -0,0 +1,66 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for building an in-memory Atom feed for a particular user's + * timeline. + * + * 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 Feed + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @copyright 2010 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); +} + +/** + * Class for user notice feeds. May contain a reference to the user. + * + * @category Feed + * @package StatusNet + * @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/ + */ +class AtomUserNoticeFeed extends AtomNoticeFeed +{ + private $user; + + /** + * Constructor + * + * @param User $user the user for the feed (optional) + * @param boolean $indent flag to turn indenting on or off + * + * @return void + */ + function __construct($user = null, $indent = true) { + parent::__construct($indent); + $this->user = $user; + } + + function getUser() + { + return $this->user; + } +} diff --git a/lib/cache.php b/lib/cache.php index b3ec7534f..c09a1dd9f 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -47,6 +47,8 @@ class Cache var $_items = array(); static $_inst = null; + const COMPRESSED = 1; + /** * Singleton constructor * @@ -133,7 +135,7 @@ class Cache * * @param string $key The key to use for lookups * @param string $value The value to store - * @param integer $flag Flags to use, mostly ignored + * @param integer $flag Flags to use, may include Cache::COMPRESSED * @param integer $expiry Expiry value, mostly ignored * * @return boolean success flag diff --git a/lib/default.php b/lib/default.php index a74cccae1..3f53edf14 100644 --- a/lib/default.php +++ b/lib/default.php @@ -117,11 +117,13 @@ $default = 'avatar' => array('server' => null, 'dir' => INSTALLDIR . '/avatar/', - 'path' => $_path . '/avatar/'), + 'path' => $_path . '/avatar/', + 'ssl' => null), 'background' => array('server' => null, 'dir' => INSTALLDIR . '/background/', - 'path' => $_path . '/background/'), + 'path' => $_path . '/background/', + 'ssl' => null), 'public' => array('localonly' => true, 'blacklist' => array(), @@ -129,10 +131,12 @@ $default = 'theme' => array('server' => null, 'dir' => null, - 'path'=> null), + 'path'=> null, + 'ssl' => null), 'javascript' => array('server' => null, - 'path'=> null), + 'path'=> null, + 'ssl' => null), 'throttle' => array('enabled' => false, // whether to throttle edits; false by default 'count' => 20, // number of allowed messages in timespan @@ -190,6 +194,7 @@ $default = array('server' => null, 'dir' => INSTALLDIR . '/file/', 'path' => $_path . '/file/', + 'ssl' => null, 'supported' => array('image/png', 'image/jpeg', 'image/gif', diff --git a/lib/error.php b/lib/error.php index 87a4d913b..a6a29119f 100644 --- a/lib/error.php +++ b/lib/error.php @@ -56,6 +56,7 @@ class ErrorAction extends Action $this->code = $code; $this->message = $message; + $this->minimal = StatusNet::isApi(); // XXX: hack alert: usually we aren't going to // call this page directly, but because it's @@ -102,7 +103,14 @@ class ErrorAction extends Action function showPage() { - parent::showPage(); + if ($this->minimal) { + // Even more minimal -- we're in a machine API + // and don't want to flood the output. + $this->extraHeaders(); + $this->showContent(); + } else { + parent::showPage(); + } // We don't want to have any more output after this exit(); diff --git a/lib/grouplist.php b/lib/grouplist.php index 99bff9cdc..854bc34e2 100644 --- a/lib/grouplist.php +++ b/lib/grouplist.php @@ -105,6 +105,7 @@ class GroupList extends Widget 'alt' => ($this->group->fullname) ? $this->group->fullname : $this->group->nickname)); + $this->out->text(' '); $hasFN = ($this->group->fullname) ? 'nickname' : 'fn org nickname'; $this->out->elementStart('span', $hasFN); $this->out->raw($this->highlight($this->group->nickname)); @@ -112,16 +113,19 @@ class GroupList extends Widget $this->out->elementEnd('a'); if ($this->group->fullname) { + $this->out->text(' '); $this->out->elementStart('span', 'fn org'); $this->out->raw($this->highlight($this->group->fullname)); $this->out->elementEnd('span'); } if ($this->group->location) { + $this->out->text(' '); $this->out->elementStart('span', 'label'); $this->out->raw($this->highlight($this->group->location)); $this->out->elementEnd('span'); } if ($this->group->homepage) { + $this->out->text(' '); $this->out->elementStart('a', array('href' => $this->group->homepage, 'class' => 'url')); $this->out->raw($this->highlight($this->group->homepage)); diff --git a/lib/groupsection.php b/lib/groupsection.php index 7327f9e1a..3b0b3029d 100644 --- a/lib/groupsection.php +++ b/lib/groupsection.php @@ -85,9 +85,9 @@ class GroupSection extends Section 'href' => $group->homeUrl(), 'rel' => 'contact group', 'class' => 'url')); + $this->out->text(' '); $logo = ($group->stream_logo) ? $group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE); - $this->out->element('img', array('src' => $logo, 'width' => AVATAR_MINI_SIZE, 'height' => AVATAR_MINI_SIZE, @@ -95,6 +95,7 @@ class GroupSection extends Section 'alt' => ($group->fullname) ? $group->fullname : $group->nickname)); + $this->out->text(' '); $this->out->element('span', 'fn org nickname', $group->nickname); $this->out->elementEnd('a'); $this->out->elementEnd('span'); diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 317f5ea61..47e56fc8f 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -376,9 +376,20 @@ class HTMLOutputter extends XMLOutputter $server = common_config('site', 'server'); } - // XXX: protocol + $ssl = common_config('javascript', 'ssl'); + + if (is_null($ssl)) { // null -> guess + if (common_config('site', 'ssl') == 'always' && + !common_config('javascript', 'server')) { + $ssl = true; + } else { + $ssl = false; + } + } + + $protocol = ($ssl) ? 'https' : 'http'; - $src = 'http://'.$server.$path.$src . '?version=' . STATUSNET_VERSION; + $src = $protocol.'://'.$server.$path.$src . '?version=' . STATUSNET_VERSION; } $this->element('script', array('type' => $type, diff --git a/lib/httpclient.php b/lib/httpclient.php index 3f8262076..4c3af8d7d 100644 --- a/lib/httpclient.php +++ b/lib/httpclient.php @@ -81,12 +81,13 @@ class HTTPResponse extends HTTP_Request2_Response } /** - * Check if the response is OK, generally a 200 status code. + * Check if the response is OK, generally a 200 or other 2xx status code. * @return bool */ function isOk() { - return ($this->getStatus() == 200); + $status = $this->getStatus(); + return ($status >= 200 && $status < 300); } } diff --git a/lib/mysqlschema.php b/lib/mysqlschema.php new file mode 100644 index 000000000..485096ac4 --- /dev/null +++ b/lib/mysqlschema.php @@ -0,0 +1,538 @@ +<?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); +} + +/** + * Class representing the database schema + * + * A class representing the database schema. Can be used to + * manipulate the schema -- especially for plugins and upgrade + * utilities. + * + * @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 MysqlSchema extends Schema +{ + static $_single = null; + protected $conn = null; + + /** + * Constructor. Only run once for singleton object. + */ + + protected function __construct() + { + // XXX: there should be an easier way to do this. + $user = new User(); + + $this->conn = $user->getDatabaseConnection(); + + $user->free(); + + unset($user); + } + + /** + * Main public entry point. Use this to get + * the singleton object. + * + * @return Schema the (single) Schema object + */ + + static function get() + { + if (empty(self::$_single)) { + self::$_single = new Schema(); + } + return self::$_single; + } + + /** + * Returns a TableDef object for the table + * in the schema with the given name. + * + * Throws an exception if the table is not found. + * + * @param string $name Name of the table to get + * + * @return TableDef tabledef for that table. + */ + + public function getTableDef($name) + { + $res = $this->conn->query('DESCRIBE ' . $name); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + $td = new TableDef(); + + $td->name = $name; + $td->columns = array(); + + $row = array(); + + while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) { + + $cd = new ColumnDef(); + + $cd->name = $row['Field']; + + $packed = $row['Type']; + + if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) { + $cd->type = $match[1]; + $cd->size = $match[2]; + } else { + $cd->type = $packed; + } + + $cd->nullable = ($row['Null'] == 'YES') ? true : false; + $cd->key = $row['Key']; + $cd->default = $row['Default']; + $cd->extra = $row['Extra']; + + $td->columns[] = $cd; + } + + return $td; + } + + /** + * Gets a ColumnDef object for a single column. + * + * Throws an exception if the table is not found. + * + * @param string $table name of the table + * @param string $column name of the column + * + * @return ColumnDef definition of the column or null + * if not found. + */ + + public function getColumnDef($table, $column) + { + $td = $this->getTableDef($table); + + foreach ($td->columns as $cd) { + if ($cd->name == $column) { + return $cd; + } + } + + return null; + } + + /** + * Creates a table with the given names and columns. + * + * @param string $name Name of the table + * @param array $columns Array of ColumnDef objects + * for new table. + * + * @return boolean success flag + */ + + public function createTable($name, $columns) + { + $uniques = array(); + $primary = array(); + $indices = array(); + + $sql = "CREATE TABLE $name (\n"; + + for ($i = 0; $i < count($columns); $i++) { + + $cd =& $columns[$i]; + + if ($i > 0) { + $sql .= ",\n"; + } + + $sql .= $this->_columnSql($cd); + + switch ($cd->key) { + case 'UNI': + $uniques[] = $cd->name; + break; + case 'PRI': + $primary[] = $cd->name; + break; + case 'MUL': + $indices[] = $cd->name; + break; + } + } + + if (count($primary) > 0) { // it really should be... + $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")"; + } + + foreach ($uniques as $u) { + $sql .= ",\nunique index {$name}_{$u}_idx ($u)"; + } + + foreach ($indices as $i) { + $sql .= ",\nindex {$name}_{$i}_idx ($i)"; + } + + $sql .= "); "; + + common_log(LOG_INFO, $sql); + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a table from the schema + * + * Throws an exception if the table is not found. + * + * @param string $name Name of the table to drop + * + * @return boolean success flag + */ + + public function dropTable($name) + { + $res = $this->conn->query("DROP TABLE $name"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Adds an index to a table. + * + * If no name is provided, a name will be made up based + * on the table name and column names. + * + * Throws an exception on database error, esp. if the table + * does not exist. + * + * @param string $table Name of the table + * @param array $columnNames Name of columns to index + * @param string $name (Optional) name of the index + * + * @return boolean success flag + */ + + public function createIndex($table, $columnNames, $name=null) + { + if (!is_array($columnNames)) { + $columnNames = array($columnNames); + } + + if (empty($name)) { + $name = "$table_".implode("_", $columnNames)."_idx"; + } + + $res = $this->conn->query("ALTER TABLE $table ". + "ADD INDEX $name (". + implode(",", $columnNames).")"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a named index from a table. + * + * @param string $table name of the table the index is on. + * @param string $name name of the index + * + * @return boolean success flag + */ + + public function dropIndex($table, $name) + { + $res = $this->conn->query("ALTER TABLE $table DROP INDEX $name"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Adds a column to a table + * + * @param string $table name of the table + * @param ColumnDef $columndef Definition of the new + * column. + * + * @return boolean success flag + */ + + public function addColumn($table, $columndef) + { + $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef); + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Modifies a column in the schema. + * + * The name must match an existing column and table. + * + * @param string $table name of the table + * @param ColumnDef $columndef new definition of the column. + * + * @return boolean success flag + */ + + public function modifyColumn($table, $columndef) + { + $sql = "ALTER TABLE $table MODIFY COLUMN " . + $this->_columnSql($columndef); + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a column from a table + * + * The name must match an existing column. + * + * @param string $table name of the table + * @param string $columnName name of the column to drop + * + * @return boolean success flag + */ + + public function dropColumn($table, $columnName) + { + $sql = "ALTER TABLE $table DROP COLUMN $columnName"; + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Ensures that a table exists with the given + * name and the given column definitions. + * + * 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 string $tableName name of the table + * @param array $columns array of ColumnDef + * objects for the table + * + * @return boolean success flag + */ + + public function ensureTable($tableName, $columns) + { + // XXX: DB engine portability -> toilet + + try { + $td = $this->getTableDef($tableName); + } catch (Exception $e) { + if (preg_match('/no such table/', $e->getMessage())) { + return $this->createTable($tableName, $columns); + } else { + throw $e; + } + } + + $cur = $this->_names($td->columns); + $new = $this->_names($columns); + + $toadd = array_diff($new, $cur); + $todrop = array_diff($cur, $new); + $same = array_intersect($new, $cur); + $tomod = array(); + + foreach ($same as $m) { + $curCol = $this->_byName($td->columns, $m); + $newCol = $this->_byName($columns, $m); + + if (!$newCol->equals($curCol)) { + $tomod[] = $newCol->name; + } + } + + if (count($toadd) + count($todrop) + count($tomod) == 0) { + // nothing to do + return true; + } + + // For efficiency, we want this all in one + // query, instead of using our methods. + + $phrase = array(); + + foreach ($toadd as $columnName) { + $cd = $this->_byName($columns, $columnName); + + $phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd); + } + + foreach ($todrop as $columnName) { + $phrase[] = 'DROP COLUMN ' . $columnName; + } + + foreach ($tomod as $columnName) { + $cd = $this->_byName($columns, $columnName); + + $phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd); + } + + $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase); + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Returns the array of names from an array of + * ColumnDef objects. + * + * @param array $cds array of ColumnDef objects + * + * @return array strings for name values + */ + + private function _names($cds) + { + $names = array(); + + foreach ($cds as $cd) { + $names[] = $cd->name; + } + + return $names; + } + + /** + * Get a ColumnDef from an array matching + * name. + * + * @param array $cds Array of ColumnDef objects + * @param string $name Name of the column + * + * @return ColumnDef matching item or null if no match. + */ + + private function _byName($cds, $name) + { + foreach ($cds as $cd) { + if ($cd->name == $name) { + return $cd; + } + } + + return null; + } + + /** + * Return the proper SQL for creating or + * altering a column. + * + * Appropriate for use in CREATE TABLE or + * ALTER TABLE statements. + * + * @param ColumnDef $cd column to create + * + * @return string correct SQL for that column + */ + + private function _columnSql($cd) + { + $sql = "{$cd->name} "; + + if (!empty($cd->size)) { + $sql .= "{$cd->type}({$cd->size}) "; + } else { + $sql .= "{$cd->type} "; + } + + if (!empty($cd->default)) { + $sql .= "default {$cd->default} "; + } else { + $sql .= ($cd->nullable) ? "null " : "not null "; + } + + if (!empty($cd->auto_increment)) { + $sql .= " auto_increment "; + } + + if (!empty($cd->extra)) { + $sql .= "{$cd->extra} "; + } + + return $sql; + } +} diff --git a/lib/noticelist.php b/lib/noticelist.php index a4a0f2651..c05b99024 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -294,6 +294,7 @@ class NoticeListItem extends Widget } $this->out->elementStart('a', $attrs); $this->showAvatar(); + $this->out->text(' '); $this->showNickname(); $this->out->elementEnd('a'); $this->out->elementEnd('span'); @@ -432,8 +433,10 @@ class NoticeListItem extends Widget $url = $location->getUrl(); + $this->out->text(' '); $this->out->elementStart('span', array('class' => 'location')); $this->out->text(_('at')); + $this->out->text(' '); if (empty($url)) { $this->out->element('span', array('class' => 'geo', 'title' => $latlon), @@ -473,9 +476,11 @@ class NoticeListItem extends Widget function showNoticeSource() { if ($this->notice->source) { + $this->out->text(' '); $this->out->elementStart('span', 'source'); $this->out->text(_('from')); $source_name = _($this->notice->source); + $this->out->text(' '); switch ($this->notice->source) { case 'web': case 'xmpp': @@ -540,6 +545,7 @@ class NoticeListItem extends Widget } } if ($hasConversation){ + $this->out->text(' '); $convurl = common_local_url('conversation', array('id' => $this->notice->conversation)); $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id, @@ -591,12 +597,14 @@ class NoticeListItem extends Widget function showReplyLink() { if (common_logged_in()) { + $this->out->text(' '); $reply_url = common_local_url('newnotice', array('replyto' => $this->profile->nickname, 'inreplyto' => $this->notice->id)); $this->out->elementStart('a', array('href' => $reply_url, 'class' => 'notice_reply', 'title' => _('Reply to this notice'))); $this->out->text(_('Reply')); + $this->out->text(' '); $this->out->element('span', 'notice_id', $this->notice->id); $this->out->elementEnd('a'); } @@ -616,7 +624,7 @@ class NoticeListItem extends Widget if (!empty($user) && ($todel->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) { - + $this->out->text(' '); $deleteurl = common_local_url('deletenotice', array('notice' => $todel->id)); $this->out->element('a', array('href' => $deleteurl, @@ -635,6 +643,7 @@ class NoticeListItem extends Widget { $user = common_current_user(); if ($user && $user->id != $this->notice->profile_id) { + $this->out->text(' '); $profile = $user->getProfile(); if ($profile->hasRepeated($this->notice->id)) { $this->out->element('span', array('class' => 'repeated', diff --git a/lib/noticesection.php b/lib/noticesection.php index 24465f8ba..7157feafc 100644 --- a/lib/noticesection.php +++ b/lib/noticesection.php @@ -90,6 +90,7 @@ class NoticeSection extends Section 'alt' => ($profile->fullname) ? $profile->fullname : $profile->nickname)); + $this->out->text(' '); $this->out->element('span', 'fn nickname', $profile->nickname); $this->out->elementEnd('a'); $this->out->elementEnd('span'); diff --git a/lib/oauthclient.php b/lib/oauthclient.php index b22fd7897..bc7587183 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -90,20 +90,47 @@ class OAuthClient /** * Gets a request token from the given url * - * @param string $url OAuth endpoint for grabbing request tokens + * @param string $url OAuth endpoint for grabbing request tokens + * @param string $callback authorized request token callback * * @return OAuthToken $token the request token */ - function getRequestToken($url) + function getRequestToken($url, $callback = null) { - $response = $this->oAuthGet($url); + $params = null; + + if (!is_null($callback)) { + $params['oauth_callback'] = $callback; + } + + $response = $this->oAuthGet($url, $params); + $arr = array(); parse_str($response, $arr); - if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) { - $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']); + + $token = $arr['oauth_token']; + $secret = $arr['oauth_token_secret']; + $confirm = $arr['oauth_callback_confirmed']; + + if (isset($token) && isset($secret)) { + + $token = new OAuthToken($token, $secret); + + if (isset($confirm)) { + if ($confirm == 'true') { + common_debug('Twitter bridge - callback confirmed.'); + return $token; + } else { + throw new OAuthClientException( + 'Callback was not confirmed by Twitter.' + ); + } + } return $token; } else { - throw new OAuthClientException(); + throw new OAuthClientException( + 'Could not get a request token from Twitter.' + ); } } @@ -113,49 +140,64 @@ class OAuthClient * * @param string $url endpoint for authorizing request tokens * @param OAuthToken $request_token the request token to be authorized - * @param string $oauth_callback optional callback url * * @return string $authorize_url the url to redirect to */ - function getAuthorizeLink($url, $request_token, $oauth_callback = null) + function getAuthorizeLink($url, $request_token) { $authorize_url = $url . '?oauth_token=' . $request_token->key; - if (isset($oauth_callback)) { - $authorize_url .= '&oauth_callback=' . urlencode($oauth_callback); - } - return $authorize_url; } /** * Fetches an access token * - * @param string $url OAuth endpoint for exchanging authorized request tokens - * for access tokens + * @param string $url OAuth endpoint for exchanging authorized request tokens + * for access tokens + * @param string $verifier 1.0a verifier * * @return OAuthToken $token the access token */ - function getAccessToken($url) + function getAccessToken($url, $verifier = null) { - $response = $this->oAuthPost($url); - parse_str($response); - $token = new OAuthToken($oauth_token, $oauth_token_secret); - return $token; + $params = array(); + + if (!is_null($verifier)) { + $params['oauth_verifier'] = $verifier; + } + + $response = $this->oAuthPost($url, $params); + + $arr = array(); + parse_str($response, $arr); + + $token = $arr['oauth_token']; + $secret = $arr['oauth_token_secret']; + + if (isset($token) && isset($secret)) { + $token = new OAuthToken($token, $secret); + return $token; + } else { + throw new OAuthClientException( + 'Could not get a access token from Twitter.' + ); + } } /** - * Use HTTP GET to make a signed OAuth request + * Use HTTP GET to make a signed OAuth requesta * - * @param string $url OAuth endpoint + * @param string $url OAuth request token endpoint + * @param array $params additional parameters * * @return mixed the request */ - function oAuthGet($url) + function oAuthGet($url, $params = null) { $request = OAuthRequest::from_consumer_and_token($this->consumer, - $this->token, 'GET', $url, null); + $this->token, 'GET', $url, $params); $request->sign_request($this->sha1_method, $this->consumer, $this->token); diff --git a/lib/pgsqlschema.php b/lib/pgsqlschema.php new file mode 100644 index 000000000..91bc09667 --- /dev/null +++ b/lib/pgsqlschema.php @@ -0,0 +1,503 @@ +<?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); +} + +/** + * Class representing the database schema + * + * A class representing the database schema. Can be used to + * manipulate the schema -- especially for plugins and upgrade + * utilities. + * + * @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 PgsqlSchema extends Schema +{ + + /** + * Returns a TableDef object for the table + * in the schema with the given name. + * + * Throws an exception if the table is not found. + * + * @param string $name Name of the table to get + * + * @return TableDef tabledef for that table. + */ + + public function getTableDef($name) + { + $res = $this->conn->query("select *, column_default as default, is_nullable as Null, udt_name as Type, column_name AS Field from INFORMATION_SCHEMA.COLUMNS where table_name = '$name'"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + $td = new TableDef(); + + $td->name = $name; + $td->columns = array(); + + $row = array(); + + while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) { +// var_dump($row); + $cd = new ColumnDef(); + + $cd->name = $row['field']; + + $packed = $row['type']; + + if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) { + $cd->type = $match[1]; + $cd->size = $match[2]; + } else { + $cd->type = $packed; + } + + $cd->nullable = ($row['null'] == 'YES') ? true : false; + $cd->key = $row['Key']; + $cd->default = $row['default']; + $cd->extra = $row['Extra']; + + $td->columns[] = $cd; + } + return $td; + } + + /** + * Gets a ColumnDef object for a single column. + * + * Throws an exception if the table is not found. + * + * @param string $table name of the table + * @param string $column name of the column + * + * @return ColumnDef definition of the column or null + * if not found. + */ + + public function getColumnDef($table, $column) + { + $td = $this->getTableDef($table); + + foreach ($td->columns as $cd) { + if ($cd->name == $column) { + return $cd; + } + } + + return null; + } + + /** + * Creates a table with the given names and columns. + * + * @param string $name Name of the table + * @param array $columns Array of ColumnDef objects + * for new table. + * + * @return boolean success flag + */ + + public function createTable($name, $columns) + { + $uniques = array(); + $primary = array(); + $indices = array(); + + $sql = "CREATE TABLE $name (\n"; + + for ($i = 0; $i < count($columns); $i++) { + + $cd =& $columns[$i]; + + if ($i > 0) { + $sql .= ",\n"; + } + + $sql .= $this->_columnSql($cd); + + switch ($cd->key) { + case 'UNI': + $uniques[] = $cd->name; + break; + case 'PRI': + $primary[] = $cd->name; + break; + case 'MUL': + $indices[] = $cd->name; + break; + } + } + + if (count($primary) > 0) { // it really should be... + $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")"; + } + + foreach ($uniques as $u) { + $sql .= ",\nunique index {$name}_{$u}_idx ($u)"; + } + + foreach ($indices as $i) { + $sql .= ",\nindex {$name}_{$i}_idx ($i)"; + } + + $sql .= "); "; + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a table from the schema + * + * Throws an exception if the table is not found. + * + * @param string $name Name of the table to drop + * + * @return boolean success flag + */ + + public function dropTable($name) + { + $res = $this->conn->query("DROP TABLE $name"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Adds an index to a table. + * + * If no name is provided, a name will be made up based + * on the table name and column names. + * + * Throws an exception on database error, esp. if the table + * does not exist. + * + * @param string $table Name of the table + * @param array $columnNames Name of columns to index + * @param string $name (Optional) name of the index + * + * @return boolean success flag + */ + + public function createIndex($table, $columnNames, $name=null) + { + if (!is_array($columnNames)) { + $columnNames = array($columnNames); + } + + if (empty($name)) { + $name = "$table_".implode("_", $columnNames)."_idx"; + } + + $res = $this->conn->query("ALTER TABLE $table ". + "ADD INDEX $name (". + implode(",", $columnNames).")"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a named index from a table. + * + * @param string $table name of the table the index is on. + * @param string $name name of the index + * + * @return boolean success flag + */ + + public function dropIndex($table, $name) + { + $res = $this->conn->query("ALTER TABLE $table DROP INDEX $name"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Adds a column to a table + * + * @param string $table name of the table + * @param ColumnDef $columndef Definition of the new + * column. + * + * @return boolean success flag + */ + + public function addColumn($table, $columndef) + { + $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef); + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Modifies a column in the schema. + * + * The name must match an existing column and table. + * + * @param string $table name of the table + * @param ColumnDef $columndef new definition of the column. + * + * @return boolean success flag + */ + + public function modifyColumn($table, $columndef) + { + $sql = "ALTER TABLE $table MODIFY COLUMN " . + $this->_columnSql($columndef); + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a column from a table + * + * The name must match an existing column. + * + * @param string $table name of the table + * @param string $columnName name of the column to drop + * + * @return boolean success flag + */ + + public function dropColumn($table, $columnName) + { + $sql = "ALTER TABLE $table DROP COLUMN $columnName"; + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Ensures that a table exists with the given + * name and the given column definitions. + * + * 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 string $tableName name of the table + * @param array $columns array of ColumnDef + * objects for the table + * + * @return boolean success flag + */ + + public function ensureTable($tableName, $columns) + { + // XXX: DB engine portability -> toilet + + try { + $td = $this->getTableDef($tableName); + } catch (Exception $e) { + if (preg_match('/no such table/', $e->getMessage())) { + return $this->createTable($tableName, $columns); + } else { + throw $e; + } + } + + $cur = $this->_names($td->columns); + $new = $this->_names($columns); + + $toadd = array_diff($new, $cur); + $todrop = array_diff($cur, $new); + $same = array_intersect($new, $cur); + $tomod = array(); + + foreach ($same as $m) { + $curCol = $this->_byName($td->columns, $m); + $newCol = $this->_byName($columns, $m); + + if (!$newCol->equals($curCol)) { + $tomod[] = $newCol->name; + } + } + + if (count($toadd) + count($todrop) + count($tomod) == 0) { + // nothing to do + return true; + } + + // For efficiency, we want this all in one + // query, instead of using our methods. + + $phrase = array(); + + foreach ($toadd as $columnName) { + $cd = $this->_byName($columns, $columnName); + + $phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd); + } + + foreach ($todrop as $columnName) { + $phrase[] = 'DROP COLUMN ' . $columnName; + } + + foreach ($tomod as $columnName) { + $cd = $this->_byName($columns, $columnName); + + $phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd); + } + + $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase); + + $res = $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Returns the array of names from an array of + * ColumnDef objects. + * + * @param array $cds array of ColumnDef objects + * + * @return array strings for name values + */ + + private function _names($cds) + { + $names = array(); + + foreach ($cds as $cd) { + $names[] = $cd->name; + } + + return $names; + } + + /** + * Get a ColumnDef from an array matching + * name. + * + * @param array $cds Array of ColumnDef objects + * @param string $name Name of the column + * + * @return ColumnDef matching item or null if no match. + */ + + private function _byName($cds, $name) + { + foreach ($cds as $cd) { + if ($cd->name == $name) { + return $cd; + } + } + + return null; + } + + /** + * Return the proper SQL for creating or + * altering a column. + * + * Appropriate for use in CREATE TABLE or + * ALTER TABLE statements. + * + * @param ColumnDef $cd column to create + * + * @return string correct SQL for that column + */ + + private function _columnSql($cd) + { + $sql = "{$cd->name} "; + + if (!empty($cd->size)) { + $sql .= "{$cd->type}({$cd->size}) "; + } else { + $sql .= "{$cd->type} "; + } + + if (!empty($cd->default)) { + $sql .= "default {$cd->default} "; + } else { + $sql .= ($cd->nullable) ? "null " : "not null "; + } + + if (!empty($cd->auto_increment)) { + $sql .= " auto_increment "; + } + + if (!empty($cd->extra)) { + $sql .= "{$cd->extra} "; + } + + return $sql; + } +} diff --git a/lib/profilelist.php b/lib/profilelist.php index 3412d41d1..693cd6449 100644 --- a/lib/profilelist.php +++ b/lib/profilelist.php @@ -191,6 +191,7 @@ class ProfileListItem extends Widget 'alt' => ($this->profile->fullname) ? $this->profile->fullname : $this->profile->nickname)); + $this->out->text(' '); $hasFN = (!empty($this->profile->fullname)) ? 'nickname' : 'fn nickname'; $this->out->elementStart('span', $hasFN); $this->out->raw($this->highlight($this->profile->nickname)); @@ -201,6 +202,7 @@ class ProfileListItem extends Widget function showFullName() { if (!empty($this->profile->fullname)) { + $this->out->text(' '); $this->out->elementStart('span', 'fn'); $this->out->raw($this->highlight($this->profile->fullname)); $this->out->elementEnd('span'); @@ -210,6 +212,7 @@ class ProfileListItem extends Widget function showLocation() { if (!empty($this->profile->location)) { + $this->out->text(' '); $this->out->elementStart('span', 'location'); $this->out->raw($this->highlight($this->profile->location)); $this->out->elementEnd('span'); @@ -219,6 +222,7 @@ class ProfileListItem extends Widget function showHomepage() { if (!empty($this->profile->homepage)) { + $this->out->text(' '); $this->out->elementStart('a', array('href' => $this->profile->homepage, 'class' => 'url')); $this->out->raw($this->highlight($this->profile->homepage)); diff --git a/lib/profilesection.php b/lib/profilesection.php index 504b1b7f7..a9482cd63 100644 --- a/lib/profilesection.php +++ b/lib/profilesection.php @@ -85,6 +85,7 @@ class ProfileSection extends Section 'href' => $profile->profileurl, 'rel' => 'contact member', 'class' => 'url')); + $this->out->text(' '); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); $this->out->element('img', array('src' => (($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_MINI_SIZE)), 'width' => AVATAR_MINI_SIZE, @@ -93,6 +94,7 @@ class ProfileSection extends Section 'alt' => ($profile->fullname) ? $profile->fullname : $profile->nickname)); + $this->out->text(' '); $this->out->element('span', 'fn nickname', $profile->nickname); $this->out->elementEnd('a'); $this->out->elementEnd('span'); diff --git a/lib/right.php b/lib/right.php index 5e66eae0e..4e9c5a918 100644 --- a/lib/right.php +++ b/lib/right.php @@ -57,5 +57,6 @@ class Right const EMAILONREPLY = 'emailonreply'; const EMAILONSUBSCRIBE = 'emailonsubscribe'; const EMAILONFAVE = 'emailonfave'; + const MAKEGROUPADMIN = 'makegroupadmin'; } diff --git a/lib/router.php b/lib/router.php index 5981ef5d7..987d0152e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -712,6 +712,10 @@ class Router 'nickname' => $nickname), array('tag' => '[a-zA-Z0-9]+')); + $m->connect('rsd.xml', + array('action' => 'rsd', + 'nickname' => $nickname)); + $m->connect('', array('action' => 'showstream', 'nickname' => $nickname)); @@ -726,6 +730,7 @@ class Router $m->connect('featured', array('action' => 'featured')); $m->connect('favorited/', array('action' => 'favorited')); $m->connect('favorited', array('action' => 'favorited')); + $m->connect('rsd.xml', array('action' => 'rsd')); foreach (array('subscriptions', 'subscribers', 'nudge', 'all', 'foaf', 'xrds', @@ -773,6 +778,10 @@ class Router array('nickname' => '[a-zA-Z0-9]{1,64}'), array('tag' => '[a-zA-Z0-9]+')); + $m->connect(':nickname/rsd.xml', + array('action' => 'rsd'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + $m->connect(':nickname', array('action' => 'showstream'), array('nickname' => '[a-zA-Z0-9]{1,64}')); diff --git a/lib/schema.php b/lib/schema.php index a7f64ebed..137b814e0 100644 --- a/lib/schema.php +++ b/lib/schema.php @@ -75,65 +75,15 @@ class Schema static function get() { + $type = common_config('db', 'type'); if (empty(self::$_single)) { - self::$_single = new Schema(); + $schemaClass = ucfirst($type).'Schema'; + self::$_single = new $schemaClass(); } return self::$_single; } /** - * Returns a TableDef object for the table - * in the schema with the given name. - * - * Throws an exception if the table is not found. - * - * @param string $name Name of the table to get - * - * @return TableDef tabledef for that table. - */ - - public function getTableDef($name) - { - $res = $this->conn->query('DESCRIBE ' . $name); - - if (PEAR::isError($res)) { - throw new Exception($res->getMessage()); - } - - $td = new TableDef(); - - $td->name = $name; - $td->columns = array(); - - $row = array(); - - while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) { - - $cd = new ColumnDef(); - - $cd->name = $row['Field']; - - $packed = $row['Type']; - - if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) { - $cd->type = $match[1]; - $cd->size = $match[2]; - } else { - $cd->type = $packed; - } - - $cd->nullable = ($row['Null'] == 'YES') ? true : false; - $cd->key = $row['Key']; - $cd->default = $row['Default']; - $cd->extra = $row['Extra']; - - $td->columns[] = $cd; - } - - return $td; - } - - /** * Gets a ColumnDef object for a single column. * * Throws an exception if the table is not found. @@ -523,7 +473,7 @@ class Schema } else { $sql .= ($cd->nullable) ? "null " : "not null "; } - + if (!empty($cd->auto_increment)) { $sql .= " auto_increment "; } diff --git a/lib/statusnet.php b/lib/statusnet.php index 9c7ede5a5..7c4df84b4 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -30,6 +30,7 @@ global $config, $_server, $_path; class StatusNet { protected static $have_config; + protected static $is_api; /** * Configure and instantiate a plugin into the current configuration. @@ -63,7 +64,7 @@ class StatusNet } } if (!class_exists($pluginclass)) { - throw new ServerException(500, "Plugin $name not found."); + throw new ServerException("Plugin $name not found.", 500); } } @@ -201,6 +202,16 @@ class StatusNet return self::$have_config; } + public function isApi() + { + return self::$is_api; + } + + public function setApi($mode) + { + self::$is_api = $mode; + } + /** * Build default configuration array * @return array diff --git a/lib/theme.php b/lib/theme.php index 020ce1ac4..0be8c3b9d 100644 --- a/lib/theme.php +++ b/lib/theme.php @@ -110,9 +110,20 @@ class Theme $server = common_config('site', 'server'); } - // XXX: protocol + $ssl = common_config('theme', 'ssl'); + + if (is_null($ssl)) { // null -> guess + if (common_config('site', 'ssl') == 'always' && + !common_config('theme', 'server')) { + $ssl = true; + } else { + $ssl = false; + } + } + + $protocol = ($ssl) ? 'https' : 'http'; - $this->path = 'http://'.$server.$path.$name; + $this->path = $protocol . '://'.$server.$path.$name; } } diff --git a/lib/userprofile.php b/lib/userprofile.php index 07e575085..43dfd05be 100644 --- a/lib/userprofile.php +++ b/lib/userprofile.php @@ -238,9 +238,12 @@ class UserProfile extends Widget if (Event::handle('StartProfilePageActionsElements', array(&$this->out, $this->profile))) { if (empty($cur)) { // not logged in - $this->out->elementStart('li', 'entity_subscribe'); - $this->showRemoteSubscribeLink(); - $this->out->elementEnd('li'); + if (Event::handle('StartProfileRemoteSubscribe', array(&$this->out, $this->profile))) { + $this->out->elementStart('li', 'entity_subscribe'); + $this->showRemoteSubscribeLink(); + $this->out->elementEnd('li'); + Event::handle('EndProfileRemoteSubscribe', array(&$this->out, $this->profile)); + } } else { if ($cur->id == $this->profile->id) { // your own page $this->out->elementStart('li', 'entity_edit'); diff --git a/lib/util.php b/lib/util.php index dd8189a58..ae812e8cf 100644 --- a/lib/util.php +++ b/lib/util.php @@ -367,7 +367,8 @@ function common_current_user() if ($_cur === false) { - if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) { + if (isset($_COOKIE[session_name()]) || isset($_GET[session_name()]) + || (isset($_SESSION['userid']) && $_SESSION['userid'])) { common_ensure_session(); $id = isset($_SESSION['userid']) ? $_SESSION['userid'] : false; if ($id) { @@ -665,6 +666,9 @@ function common_valid_profile_tag($str) function common_at_link($sender_id, $nickname) { $sender = Profile::staticGet($sender_id); + if (!$sender) { + return $nickname; + } $recipient = common_relative_profile($sender, common_canonical_nickname($nickname)); if ($recipient) { $user = User::staticGet('id', $recipient->id); @@ -694,7 +698,7 @@ function common_group_link($sender_id, $nickname) { $sender = Profile::staticGet($sender_id); $group = User_group::getForNickname($nickname); - if ($group && $sender->isMember($group)) { + if ($sender && $group && $sender->isMember($group)) { $attrs = array('href' => $group->permalink(), 'class' => 'url'); if (!empty($group->fullname)) { @@ -996,9 +1000,13 @@ function common_enqueue_notice($notice) static $localTransports = array('omb', 'ping'); - static $allTransports = array('sms', 'plugin'); - - $transports = $allTransports; + $transports = array(); + if (common_config('sms', 'enabled')) { + $transports[] = 'sms'; + } + if (Event::hasHandler('HandleQueuedNotice')) { + $transports[] = 'plugin'; + } $xmpp = common_config('xmpp', 'enabled'); @@ -1006,6 +1014,7 @@ function common_enqueue_notice($notice) $transports[] = 'jabber'; } + // @fixme move these checks into QueueManager and/or individual handlers if ($notice->is_local == Notice::LOCAL_PUBLIC || $notice->is_local == Notice::LOCAL_NONPUBLIC) { $transports = array_merge($transports, $localTransports); @@ -1564,3 +1573,56 @@ function common_client_ip() return array($proxy, $ip); } + +function common_url_to_nickname($url) +{ + static $bad = array('query', 'user', 'password', 'port', 'fragment'); + + $parts = parse_url($url); + + # If any of these parts exist, this won't work + + foreach ($bad as $badpart) { + if (array_key_exists($badpart, $parts)) { + return null; + } + } + + # We just have host and/or path + + # If it's just a host... + if (array_key_exists('host', $parts) && + (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0)) + { + $hostparts = explode('.', $parts['host']); + + # Try to catch common idiom of nickname.service.tld + + if ((count($hostparts) > 2) && + (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au + (strcmp($hostparts[0], 'www') != 0)) + { + return common_nicknamize($hostparts[0]); + } else { + # Do the whole hostname + return common_nicknamize($parts['host']); + } + } else { + if (array_key_exists('path', $parts)) { + # Strip starting, ending slashes + $path = preg_replace('@/$@', '', $parts['path']); + $path = preg_replace('@^/@', '', $path); + if (strpos($path, '/') === false) { + return common_nicknamize($path); + } + } + } + + return null; +} + +function common_nicknamize($str) +{ + $str = preg_replace('/\W/', '', $str); + return strtolower($str); +} |