]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.9.x' into refactor-api
authorZach Copley <zach@status.net>
Thu, 1 Oct 2009 23:17:43 +0000 (16:17 -0700)
committerZach Copley <zach@status.net>
Thu, 1 Oct 2009 23:17:43 +0000 (16:17 -0700)
* 0.9.x:
  change DB so OpenIDPlugin manages OpenID tables
  Some bug fixes
  check the schema
  add some more methods to Schema
  Added hook for Aside container
  Revert "Outputting UTF-8 charset in document header irrespective of mimetype."
  FOAF for Groups.
  use schema tool to create a table
  statusize schema-related modules
  test script for schema code
  make table def method of schema code work
  start a module for schema management

15 files changed:
EVENTS.txt
README
actions/foafgroup.php [new file with mode: 0644]
actions/showgroup.php
db/statusnet.sql
lib/action.php
lib/common.php
lib/default.php
lib/htmloutputter.php
lib/router.php
lib/schema.php [new file with mode: 0644]
lib/util.php
plugins/OpenID/OpenIDPlugin.php
scripts/checkschema.php [new file with mode: 0644]
scripts/showtable.php [new file with mode: 0644]

index 74923dcc0a8301132a34c09e4a0542ce215885ae..fbb2f36a7b81ff2987213cdaecca53749bae31a4 100644 (file)
@@ -87,6 +87,12 @@ StartShowContentBlock: Showing before the content container
 EndShowContentBlock: Showing after the content container
 - $action: the current action
 
+StartShowAside: Showing before the Aside container
+- $action: the current action
+
+EndShowAside: Showing after the Aside container
+- $action: the current action
+
 StartNoticeSave: before inserting a notice (good place for content filters)
 - $notice: notice being saved (no ID or URI)
 
@@ -277,3 +283,5 @@ StartShowHeadElements: Right after the <head> tag
 
 EndShowHeadElements: Right before the </head> tag; put <script>s here if you need them in <head>
 - $action: the current action
+
+CheckSchema: chance to check the schema
diff --git a/README b/README
index f3b2528b85ab6bd1932ce997badd5266efb65f73..486656a3bcf178b65f366ca6c34ce70f3f51510f 100644 (file)
--- a/README
+++ b/README
@@ -1037,6 +1037,14 @@ utf8: whether to talk to the database in UTF-8 mode. This is the default
       with new installations, but older sites may want to turn it off
       until they get their databases fixed up. See "UTF-8 database"
       above for details.
+schemacheck: when to let plugins check the database schema to add
+             tables or update them. Values can be 'runtime' (default)
+             or 'script'. 'runtime' can be costly (plugins check the
+             schema on every hit, adding potentially several db
+             queries, some quite long), but not everyone knows how to
+             run a script. If you can, set this to 'script' and run
+             scripts/checkschema.php whenever you install or upgrade a
+             plugin.
 
 syslog
 ------
diff --git a/actions/foafgroup.php b/actions/foafgroup.php
new file mode 100644 (file)
index 0000000..f5fd7fe
--- /dev/null
@@ -0,0 +1,173 @@
+<?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/>.
+ *
+ * @category  Mail
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Toby Inkster <mail@tobyinkster.co.uk>
+ * @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); }
+
+class FoafGroupAction extends Action
+{
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $nickname_arg = $this->arg('nickname');
+
+        if (empty($nickname_arg)) {
+            $this->clientError(_('No such group.'), 404);
+            return false;
+        }
+
+        $this->nickname = common_canonical_nickname($nickname_arg);
+
+        // Permanent redirect on non-canonical nickname
+
+        if ($nickname_arg != $this->nickname) {
+            common_redirect(common_local_url('foafgroup',
+                                             array('nickname' => $this->nickname)),
+                            301);
+            return false;
+        }
+
+        $this->group = User_group::staticGet('nickname', $this->nickname);
+
+        if (!$this->group) {
+            $this->clientError(_('No such group.'), 404);
+            return false;
+        }
+
+        common_set_returnto($this->selfUrl());
+
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        header('Content-Type: application/rdf+xml');
+
+        $this->startXML();
+        $this->elementStart('rdf:RDF', array('xmlns:rdf' =>
+                                              'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+                                              'xmlns:dcterms' =>
+                                              'http://purl.org/dc/terms/',
+                                              'xmlns:sioc' =>
+                                              'http://rdfs.org/sioc/ns#',
+                                              'xmlns:foaf' =>
+                                              'http://xmlns.com/foaf/0.1/',
+                                              'xmlns:statusnet' =>
+                                              'http://status.net/ont/',
+                                              'xmlns' => 'http://xmlns.com/foaf/0.1/'));
+
+        $this->showPpd(common_local_url('foafgroup', array('nickname' => $this->nickname)), $this->group->permalink());
+
+        $this->elementStart('Group', array('rdf:about' =>
+                                             $this->group->permalink()));
+        if ($this->group->fullname) {
+            $this->element('name', null, $this->group->fullname);
+        }
+        if ($this->group->description) {
+            $this->element('dcterms:description', null, $this->group->description);
+        }
+        if ($this->group->nickname) {
+            $this->element('dcterms:identifier', null, $this->group->nickname);
+            $this->element('nick', null, $this->group->nickname);
+        }
+        foreach ($this->group->getAliases() as $alias) {
+            $this->element('nick', null, $alias);
+        }
+        if ($this->group->homeUrl()) {
+            $this->element('weblog', array('rdf:resource' => $this->group->homeUrl()));
+        }
+        if ($this->group->homepage) {
+            $this->element('page', array('rdf:resource' => $this->group->homepage));
+        }
+        if ($this->group->homepage_logo) {
+            $this->element('depiction', array('rdf:resource' => $this->group->homepage_logo));
+        }
+        
+        $members = $this->group->getMembers();
+        $member_details = array();
+        while ($members->fetch()) {
+            $member_uri = common_local_url('userbyid', array('id'=>$members->id));
+            $member_details[$member_uri] = array(
+                                        'nickname' => $members->nickname
+                                        );
+            $this->element('member', array('rdf:resource' => $member_uri));
+        }
+        
+        $admins = $this->group->getAdmins();
+        while ($admins->fetch()) {
+            $admin_uri = common_local_url('userbyid', array('id'=>$admins->id));
+            $member_details[$admin_uri]['is_admin'] = true;
+            $this->element('statusnet:groupAdmin', array('rdf:resource' => $admin_uri));
+        }
+
+        $this->elementEnd('Group');
+        
+        ksort($member_details);
+        foreach ($member_details as $uri => $details) {
+            if ($details['is_admin'])
+            {
+                $this->elementStart('Agent', array('rdf:about' => $uri));
+                $this->element('nick', null, $details['nickname']);
+                $this->elementStart('holdsAccount');
+                $this->elementStart('sioc:User', array('rdf:about'=>$uri.'#acct'));
+                $this->elementStart('sioc:has_function');
+                $this->elementStart('statusnet:GroupAdminRole');
+                $this->element('sioc:scope', array('rdf:resource' => $this->group->permalink()));
+                $this->elementEnd('statusnet:GroupAdminRole');
+                $this->elementEnd('sioc:has_function');
+                $this->elementEnd('sioc:User');
+                $this->elementEnd('holdsAccount');
+                $this->elementEnd('Agent');
+            }
+            else
+            {
+                $this->element('Agent', array(
+                                        'foaf:nick' => $details['nickname'],
+                                        'rdf:about' => $uri,
+                                        ));
+            }
+        }
+        
+        $this->elementEnd('rdf:RDF');
+        $this->endXML();
+    }
+
+    function showPpd($foaf_url, $person_uri)
+    {
+        $this->elementStart('Document', array('rdf:about' => $foaf_url));
+        $this->element('primaryTopic', array('rdf:resource' => $person_uri));
+        $this->elementEnd('Document');
+    }
+
+}
\ No newline at end of file
index ff994976215263447b6151d5f65b131873c1039f..a67765ce583c94b39f6cab9d967217ba7d4c65a6 100644 (file)
@@ -345,7 +345,12 @@ class ShowgroupAction extends GroupDesignAction
                                                      'method' => 'timeline',
                                                      'argument' => $this->group->nickname.'.atom')),
                               sprintf(_('Notice feed for %s group (Atom)'),
-                                      $this->group->nickname)));
+                                      $this->group->nickname)),
+                     new Feed(Feed::FOAF,
+                              common_local_url('foafgroup',
+                                               array('nickname' => $this->group->nickname)),
+                              sprintf(_('FOAF for %s group'),
+                                       $this->group->nickname)));
     }
 
     /**
index 221d60ce37a6b336ee6bf0be95bae0e44226d96b..dfcddb643c8b8004d1043c73053329c69c45852a 100644 (file)
@@ -195,18 +195,6 @@ create table nonce (
     constraint primary key (consumer_key, ts, nonce)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
-/* One-to-many relationship of user to openid_url */
-
-create table user_openid (
-    canonical varchar(255) primary key comment 'Canonical true URL',
-    display varchar(255) not null unique key comment 'URL for viewing, may be different from canonical',
-    user_id integer not null comment 'user owning this URL' references user (id),
-    created datetime not null comment 'date this record was created',
-    modified timestamp comment 'date this record was modified',
-
-    index user_openid_user_id_idx (user_id)
-) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-
 /* These are used by JanRain OpenID library */
 
 create table oid_associations (
index 02793f0694ed05e5e123a64fca2d122afe541678..71ceffe20dbf9a5d2b033814567e99a188547c64 100644 (file)
@@ -525,7 +525,10 @@ class Action extends HTMLOutputter // lawsuit
             $this->showContentBlock();
             Event::handle('EndShowContentBlock', array($this));
         }
-        $this->showAside();
+        if (Event::handle('StartShowAside', array($this))) {
+            $this->showAside();
+            Event::handle('EndShowAside', array($this));
+        }
         $this->elementEnd('div');
     }
 
index 58e208a4e92b98620fb540c7ffbafb67904d21d3..ce33c871bfb15b83d1c0e4b207952c8a7b61cb54 100644 (file)
@@ -232,6 +232,12 @@ require_once INSTALLDIR.'/lib/serverexception.php';
 
 Config::loadSettings();
 
+// XXX: if plugins should check the schema at runtime, do that here.
+
+if ($config['db']['schemacheck'] == 'runtime') {
+    Event::handle('CheckSchema');
+}
+
 // XXX: other formats here
 
 define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
index 7af94d2ad6081c11683f1eceb2206f741214c9f6..f9670cb7f96523c1122ff6b7cbc970e6eb50f67c 100644 (file)
@@ -64,7 +64,8 @@ $default =
               'utf8' => true,
               'db_driver' => 'DB', # XXX: JanRain libs only work with DB
               'quote_identifiers' => false,
-              'type' => 'mysql' ),
+              'type' => 'mysql',
+              'schemacheck' => 'runtime'), // 'runtime' or 'script'
         'syslog' =>
         array('appname' => 'statusnet', # for syslog
               'priority' => 'debug', # XXX: currently ignored
index 64be745bebe85daa14998d451df7eb5bddda4701..c70f965376af91bea815d604b765ca469b9e2484 100644 (file)
@@ -106,7 +106,7 @@ class HTMLOutputter extends XMLOutputter
             }
         }
 
-        header('Content-Type: '.$type.'; charset=UTF-8');
+        header('Content-Type: '.$type);
 
         $this->extraHeaders();
         if (preg_match("/.*\/.*xml/", $type)) {
index 1ea06afe08a7a66bf435421560f31d238aaafb02..3de4e322f9e7d53ad06f131603a19317c7d1a911 100644 (file)
@@ -241,6 +241,10 @@ class Router
                         array('nickname' => '[a-zA-Z0-9]+'));
         }
 
+        $m->connect('group/:nickname/foaf',
+                    array('action' => 'foafgroup'),
+                    array('nickname' => '[a-zA-Z0-9]+'));
+
         $m->connect('group/:nickname/blocked',
                     array('action' => 'blockedfromgroup'),
                     array('nickname' => '[a-zA-Z0-9]+'));
diff --git a/lib/schema.php b/lib/schema.php
new file mode 100644 (file)
index 0000000..8c8f5e9
--- /dev/null
@@ -0,0 +1,445 @@
+<?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 Schema
+{
+    static $_single = null;
+    protected $conn = null;
+
+    protected function __construct()
+    {
+        // XXX: there should be an easier way to do this.
+        $user = new User();
+        $this->conn = $user->getDatabaseConnection();
+        $user->free();
+        unset($user);
+    }
+
+    static function get()
+    {
+        if (empty(self::$_single)) {
+            self::$_single = new Schema();
+        }
+        return self::$_single;
+    }
+
+    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;
+    }
+
+    public function getColumnDef($table, $column)
+    {
+        $td = $this->getTableDef($table);
+
+        foreach ($td->columns as $cd) {
+            if ($cd->name == $column) {
+                return $cd;
+            }
+        }
+
+        return null;
+    }
+
+    public function getIndexDef($table, $index)
+    {
+        return null;
+    }
+
+    public function createTable($name, $columns, $indices=null)
+    {
+        $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_debug($sql);
+
+        $res =& $this->conn->query($sql);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    public function dropTable($name)
+    {
+        $res =& $this->conn->query("DROP TABLE $name");
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    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;
+    }
+
+    public function ensureTable($tableName, $columns, $indices=null)
+    {
+        // 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, $indices);
+            } 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;
+    }
+
+    function _names($cds)
+    {
+        $names = array();
+
+        foreach ($cds as $cd) {
+            $names[] = $cd->name;
+        }
+
+        return $names;
+    }
+
+    function _byName($cds, $name)
+    {
+        foreach ($cds as $cd) {
+            if ($cd->name == $name) {
+                return $cd;
+            }
+        }
+
+        return null;
+    }
+
+    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 ";
+        }
+
+        return $sql;
+    }
+}
+
+class TableDef
+{
+    public $name;
+    public $columns;
+}
+
+class ColumnDef
+{
+    public $name;
+    public $type;
+    public $size;
+    public $nullable;
+    public $key;
+    public $default;
+    public $extra;
+
+    function __construct($name=null, $type=null, $size=null,
+                         $nullable=true, $key=null, $default=null,
+                         $extra=null) {
+        $this->name     = strtolower($name);
+        $this->type     = strtolower($type);
+        $this->size     = $size+0;
+        $this->nullable = $nullable;
+        $this->key      = $key;
+        $this->default  = $default;
+        $this->extra    = $extra;
+    }
+
+    function equals($other)
+    {
+        return ($this->name == $other->name &&
+                $this->_typeMatch($other) &&
+                $this->_defaultMatch($other) &&
+                $this->_nullMatch($other) &&
+                $this->key == $other->key);
+    }
+
+    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);
+        }
+    }
+
+    function _defaultMatch($other)
+    {
+        return ((is_null($this->default) && is_null($other->default)) ||
+                ($this->default == $other->default));
+    }
+
+    function _nullMatch($other)
+    {
+        return ((!is_null($this->default) && !is_null($other->default) &&
+                 $this->default == $other->default) ||
+                ($this->nullable == $other->nullable));
+    }
+}
+
+class IndexDef
+{
+    public $name;
+    public $table;
+    public $columns;
+}
index 44a377220016ea08347426f5b1bdc97241b930db..d249b154fc7b17bbf5135438c30e93f4ccfcceb3 100644 (file)
@@ -1165,7 +1165,7 @@ function common_negotiate_type($cprefs, $sprefs)
     }
 
     if ('text/html' === $besttype) {
-        return "text/html";
+        return "text/html; charset=utf-8";
     }
     return $besttype;
 }
index 91bddf381f4f989ee41362eb83fdba083feea173..a933a1155510296983c2cd1bf2cdcbc7d2776bb9 100644 (file)
@@ -222,4 +222,19 @@ class OpenIDPlugin extends Plugin
 
         return true;
     }
+
+    function onCheckSchema() {
+        $schema = Schema::get();
+        $schema->ensureTable('user_openid',
+                             array(new ColumnDef('canonical', 'varchar',
+                                                 '255', false, 'PRI'),
+                                   new ColumnDef('display', 'varchar',
+                                                 '255', false),
+                                   new ColumnDef('user_id', 'integer',
+                                                 null, false, 'MUL'),
+                                   new ColumnDef('created', 'datetime',
+                                                 null, false),
+                                   new ColumnDef('modified', 'timestamp')));
+        return true;
+    }
 }
diff --git a/scripts/checkschema.php b/scripts/checkschema.php
new file mode 100644 (file)
index 0000000..bf52abe
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<END_OF_CHECKSCHEMA_HELP
+Gives plugins a chance to update the database schema.
+
+END_OF_CHECKSCHEMA_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+Event::handle('CheckSchema');
diff --git a/scripts/showtable.php b/scripts/showtable.php
new file mode 100644 (file)
index 0000000..eb18a98
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<END_OF_SHOWTABLE_HELP
+showtable.php <tablename>
+Shows the structure of a table
+
+END_OF_SHOWTABLE_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (count($args) != 1) {
+    show_help();
+}
+
+$name = $args[0];
+
+$schema = Schema::get();
+
+$td = $schema->getTableDef($name);
+
+print_r($td);