diff options
author | Sarven Capadisli <csarven@status.net> | 2009-10-02 10:05:16 +0200 |
---|---|---|
committer | Sarven Capadisli <csarven@status.net> | 2009-10-02 10:05:16 +0200 |
commit | 2cc1ff74eac9c7691639ea277b0dd5a338b7a6de (patch) | |
tree | 0cb37b09233d85fe834bb9ad8cc0b847e4b3faf6 | |
parent | b5d9d0562ad9da2fbed37e61ab792faa0ba4e7b0 (diff) | |
parent | f65baaaa4f48b84392200bb54161887669440cab (diff) |
Merge branch '0.9.x' of git@gitorious.org:statusnet/mainline into 0.9.x
-rw-r--r-- | EVENTS.txt | 8 | ||||
-rw-r--r-- | README | 8 | ||||
-rw-r--r-- | db/statusnet.sql | 12 | ||||
-rw-r--r-- | lib/action.php | 5 | ||||
-rw-r--r-- | lib/common.php | 6 | ||||
-rw-r--r-- | lib/default.php | 3 | ||||
-rw-r--r-- | lib/htmloutputter.php | 2 | ||||
-rw-r--r-- | lib/schema.php | 445 | ||||
-rw-r--r-- | lib/util.php | 2 | ||||
-rw-r--r-- | plugins/OpenID/OpenIDPlugin.php | 15 | ||||
-rw-r--r-- | scripts/checkschema.php | 30 | ||||
-rw-r--r-- | scripts/showtable.php | 41 |
12 files changed, 561 insertions, 16 deletions
diff --git a/EVENTS.txt b/EVENTS.txt index 74923dcc0..fbb2f36a7 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -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 @@ -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/db/statusnet.sql b/db/statusnet.sql index 221d60ce3..dfcddb643 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -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 ( diff --git a/lib/action.php b/lib/action.php index 02793f069..71ceffe20 100644 --- a/lib/action.php +++ b/lib/action.php @@ -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'); } diff --git a/lib/common.php b/lib/common.php index 58e208a4e..ce33c871b 100644 --- a/lib/common.php +++ b/lib/common.php @@ -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); diff --git a/lib/default.php b/lib/default.php index 7af94d2ad..f9670cb7f 100644 --- a/lib/default.php +++ b/lib/default.php @@ -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 diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 64be745be..c70f96537 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -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)) { diff --git a/lib/schema.php b/lib/schema.php new file mode 100644 index 000000000..8c8f5e9ff --- /dev/null +++ b/lib/schema.php @@ -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; +} diff --git a/lib/util.php b/lib/util.php index 44a377220..d249b154f 100644 --- a/lib/util.php +++ b/lib/util.php @@ -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; } diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 91bddf381..a933a1155 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -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 index 000000000..bf52abe15 --- /dev/null +++ b/scripts/checkschema.php @@ -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 index 000000000..eb18a98e2 --- /dev/null +++ b/scripts/showtable.php @@ -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); |